Commit Graph

1206 Commits

Author SHA1 Message Date
dante01yoon
456cf7227e fix(billing): keep both credits-row buttons inside the user popover
A cancelled-but-still-active personal subscription renders both "Add credits" and "Resubscribe" in the credits row. With a wide (7-digit) balance the second button overflowed the fixed-width w-80 popover. Make the popover width fluid (w-fit, clamped min-w-80..max-w-96) so it stays 320px in the common single-action case and only grows (to ~370px) to fit both buttons.

Adds a @cloud e2e that asserts the Resubscribe button stays within the popover bounds.
2026-06-20 23:04:09 +09:00
dante01yoon
ab082bf952 test(billing): add @cloud e2e for the pricing deep link (FE-1104)
(cherry picked from commit c9babcdb85)
2026-06-19 23:40:18 +09:00
dante01yoon
f528e6b8b6 merge: FE-966 latest (#12953) into FE-991 integration
- runtime billing-type reinit fix (watch [workspaceId, type] -> reinit on type flip)
- adopt FE-966 flag-only billing dispatch: drop the stale personalWorkspaceBillingReady gate (absent on main + FE-966-latest; leftover from an earlier FE-966 merge)
- keep integrated #12975 subscribe(planSlug, options) signature + FE-964 topup validation
- useBillingContext.test.ts taken from FE-966-latest (flag-only reactive mock + reinit tests)
2026-06-19 12:16:52 +09:00
AustinMroz
7f25d28b71 Filter canvasOnly non-preview widgets in editor (#12957)
Non preview, `canvasOnly` widgets like `control_after_generate` could be
displayed in the subgraph editor even though promoting them would have
no visual or functional effect when in vue mode.

In vue mode, these entries are now hidden from the list of candidate
items for promotion to reduce confusion.
2026-06-19 00:59:00 +00:00
Dante
67b884d0f7 fix(billing): route subscription/sign-in/credit preconditions to modal, out of error panel (FE-878) (#12785)
## Summary

Account preconditions (sign-in / subscription / credits) on running a
workflow now open their modal directly and stay out of the error panel +
error count — previously `subscription_required` fell through to a red
"1 ERROR — Subscription required to queue workflows" banner. This covers
**both** paths: the `execution_error` websocket event and the `POST
/prompt` 402 queue paywall (`{ type: "PAYMENT_REQUIRED", message:
"Subscription required to queue workflows" }`), which is the exact
payload reported in #12840.

## Changes

- **What**: `execution_error` is classified by a pure
`accountPreconditionRouting` resolver (precedence sign-in > subscription
> credits) and routed to the existing modal via
`useAccountPreconditionDialog`; `executionStore` returns early for
preconditions so they never populate `lastExecutionError` /
`lastPromptError` / `lastNodeErrors` → fully excluded from the panel and
`totalErrorCount`. Runtime credit error at a node → credits modal (out
of panel; can name the node).
- **Queue paywall**: the `queuePrompt` catch resolves the same
precondition from the `POST /prompt` 402 response and opens the modal,
short-circuiting before `lastPromptError`, so the queue paywall stays
out of the panel too. The runtime matcher learns the `"Subscription
required to queue workflows"` message.
- **Breaking**: none.

## Before / After

Free-tier queue paywall (`POST /prompt` → 402) on a cloud build:

**Before** — raw `Subscription required to queue workflows` surfaced in
the error panel (no actionable upgrade):

<img width="1600" height="873" alt="before-error-panel"
src="https://github.com/user-attachments/assets/1b76b742-16bf-47e3-9245-17e35f8f1e70"
/>

**After** — clean subscription modal opens; nothing in the error panel
or error count:

<img width="1600" height="873" alt="after-subscription-modal"
src="https://github.com/user-attachments/assets/13d238cb-20bf-4795-a530-5abcf9968dc7"
/>

## Review Focus

- **Routing-only — the run button is intentionally untouched.** The
original AC#3 ("no Subscribe-to-Run button") is superseded by the FE-978
run-lock decision (pre-emptive role-aware lock, Figma 3253-18671).
Complements #12786 (FE-978 run-lock); disjoint file sets.
- Tests: `accountPreconditionRouting` / `useAccountPreconditionDialog` /
`executionStore` — each precondition routes to its modal and is excluded
from the panel/count; precedence resolves on co-occurrence. Plus
Playwright `browser_tests/tests/subscriptionPaywallError.spec.ts` — the
queue paywall (402) stays out of the error panel, with a control
asserting ordinary queue errors still surface. typecheck / oxlint /
eslint / stylelint / oxfmt / knip clean.

Fixes FE-878
Fixes #12840
2026-06-19 00:51:52 +00:00
Alexander Brown
ab6c44aabf feat: remove deprecated group nodes, auto-convert to subgraphs on load (#12931)
## Summary

Removes the deprecated Group Nodes feature and replaces it with a
load-time migration that auto-converts any group nodes in a loaded
workflow into Subgraphs (with accepted lossiness).

## Changes

- **What**:
- `groupNode.ts` is now a migration-only extension.
`beforeConfigureGraph` registers temporary node types from
`extra.groupNodes` so instances are created during `configure`; a new
`afterConfigureGraph` hook converts every group node in the root graph
to a subgraph (via `LGraph.convertToSubgraph`), re-scanning until none
remain, then deletes `extra.groupNodes`. A failed conversion removes the
offending node so loading never hangs or breaks.
- Kept the minimum needed: `GroupNodeConfig` (builds the
input/output/widget maps), a slimmed `GroupNodeHandler` exposing a
rewritten `convertToNodes()` that no longer depends on the execution
DTOs, the `globalDefs`/`addCustomNodeDefs` path, and the `nodeDefStore`
`Object.assign` shim the migration relies on to detect group nodes.
- Deleted: the Manage Group Nodes dialog (`groupNodeManage.ts`/`.css`),
execution DTOs (`executableGroupNodeDto.ts`,
`executableGroupNodeChildDTO.ts`), the create/builder flow, recreate,
commands, keybindings, menus, the `isGroupNode` branches in the
right-side panel / error grouping / focus composable, the group-node
branches in node templates, dead i18n keys, and the now-unused
`serialise` clipboard helper.
- Rewrote `browser_tests/tests/groupNode.spec.ts` to assert
auto-conversion; deleted the `ManageGroupNode` page object and
`manageGroupNode()` helper.
  - Net: ~2,700 lines removed across 23 files (7 files deleted).
- **Breaking**: Group nodes can no longer be created, managed, or
executed. Existing workflows still load — their group nodes are
converted to subgraphs on open.

## Review Focus

- The load-time migration in `afterConfigureGraph` and the rewritten
`GroupNodeHandler.convertToNodes()` (no longer uses the execution
`getInnerNodes()` / DTOs; derives inner node type/index from
`groupData.nodeData.nodes` and relies on `deserialiseAndCreate` +
selection ordering).
- Kept `nodeDefStore`'s `Object.assign(this, obj)` shim: the migration
depends on it to propagate the group-node marker symbol onto the
registered node definition.

### Accepted lossiness
- Group nodes nested inside subgraphs (or inside other group nodes)
convert into the root graph rather than their original container —
essentially nonexistent in real legacy workflows since group nodes
predate subgraphs.
- Temporary `workflow>name` node types stay registered for the session;
instantiating one auto-converts it to a subgraph.

## Verification

`pnpm typecheck`, `typecheck:browser`, `knip`, `oxlint`, `eslint`, and
`oxfmt` are green (also enforced by pre-commit hooks). Unit tests for
the touched files could not be run locally due to a pre-existing
environment error (`file:///assets/images/*.svg` passed to a Node
filename API at import time, which also fails on unmodified test files);
the browser spec requires a live server.

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: AustinMroz <austin@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
2026-06-18 21:16:24 +00:00
Dante
2cdaead000 fix(cloud): stop bouncing working users to /cloud/survey mid-session (FE-739) (#12621)
## Summary

Cloud users get yanked to `/cloud/survey` mid-workflow with no user
action. The redirect is **downstream of auth**: when the Cloud token is
briefly stale (token rotation / auth-refresh / reconnect window), the
authenticated survey-status check 401s, and the gate turned that
transient 401 into "survey not completed" → redirect.

Surveys are currently disabled for everyone on cloud via dynamicconfig
as the live mitigation. This PR lets us re-enable them safely
**without** waiting on the auth rework.

## Root cause

`getSurveyCompletedStatus()` returned `false` ("not completed") on
**any** non-200 — including a transient 401/403/5xx or network error —
and consumers treat `false` as a redirect to the survey. So a
stale-token 401 (or the page force-reload a 401 triggers in
`GraphCanvas`) bounced a working, already-onboarded user to the survey.

The real root cause of the transient 401s is a separate, still-open
effort: **FE-963** (reactive 401 re-mint + single retry), **FE-950/951**
(unified Cloud JWT), **BE-1125**. This PR does **not** fix those; it
stops the survey from being their user-visible casualty.

## The fix

`getSurveyCompletedStatus` now distinguishes the responses instead of
failing closed on all of them:

- **404** → not completed (show survey). This is the genuine signal: the
cloud backend (`GetSettingById`) returns 404 for a survey key that was
never stored, and a 404 is only reachable after a successful
authenticated read (a stale token 401s, never 404s), so it can't be a
transient false signal.
- **transient 401/403/5xx/network** → treat as completed (fail-safe), so
a working user is never bounced.
- **200** → completed iff `value` is non-empty (unchanged).

**No router change.** The `/` onboarding guard is untouched (router.ts
matches main), so the existing UX is preserved — a not-completed user is
still gated to the survey on load; only the spurious transient-failure
bounce is removed.

## Why #12301 was reverted, and how this differs

#12301 shipped a **blanket** fail-safe (`!response.ok → true`, 404
included), which made the survey unreachable for genuinely-not-completed
users (404 → "completed") and was reverted in #12344. This PR
special-cases **404 as the real not-completed signal** and fails safe
only on transient/ambiguous responses, so onboarding still works.

## Tests

- **Unit** (`auth.test.ts`): 200 non-empty → true; 200 empty / `null` /
missing `value` key → false; **404 → false**; 401/403/500/network →
true.
- **E2E** (`browser_tests/tests/cloudSurveyGate.spec.ts`, `@cloud`): a
transient 401 on `/` does **not** bounce a working user; a genuine 404
on `/` **does** route to the survey.

Linear: FE-739. Root cause (separate): FE-963 / FE-950 / FE-951 /
BE-1125.
2026-06-18 20:52:41 +00:00
AustinMroz
1a27372e44 Fix 'insert as node' in sidebar tab (#12900)
When right clicking an output asset from the assets sidebar panel, the
'insert as node in workflow' action was twice bugged
- The default type, as used for determining filename annotation, was set
to the type of the file. This meant that annotations would never be
applied to the filename
- `temp` outputs would incorrectly be assigned the `output` type.
- My fix for this one gives me a slightly bad taste in my mouth. Parsing
URLs isn't great, but it's cleaner than needing to scan the (potentially
sparse) full outputs to try and find the corresponding output.
2026-06-18 13:48:02 -07:00
dante01yoon
145ffd4c0c merge: re-sync FE-878 precondition routing tip (#12785) into FE-991 2026-06-19 00:14:29 +09:00
dante01yoon
9968380f1c Merge remote-tracking branch 'origin/jaewon/fe-770-promote-demote-workspace-members-owners-settings-members' into jaewon/fe-991-e2e-integration-pass-for-billing-rework-v1-team-workspaces 2026-06-18 20:32:38 +09:00
dante01yoon
68de563907 Merge remote-tracking branch 'origin/main' into jaewon/fe-991-e2e-integration-pass-for-billing-rework-v1-team-workspaces
# Conflicts:
#	browser_tests/tests/workspaceSwitcher.spec.ts
#	src/platform/cloud/subscription/composables/useSubscriptionDialog.ts
#	src/platform/workspace/composables/useWorkspaceBilling.test.ts
2026-06-18 20:31:16 +09:00
dante01yoon
675d04af2b test(workspace): fix stale promote-dialog copy assertions (FE-770)
Promote dialog now renders promoteIntro + a permissions list instead of the single promoteMessage sentence; update the unit and cloud e2e assertions to match the rendered copy.
2026-06-18 20:08:12 +09:00
dante01yoon
3a480d17e7 Merge remote-tracking branch 'origin/jaewon/fe-978-owner-lifecycle-gating' into jaewon/fe-991-e2e-integration-pass-for-billing-rework-v1-team-workspaces
# Conflicts:
#	src/platform/workspace/api/workspaceApi.ts
#	src/platform/workspace/components/SubscriptionPanelContentWorkspace.vue
#	src/platform/workspace/components/TopUpCreditsDialogContentWorkspace.vue
2026-06-18 16:53:48 +09:00
dante01yoon
917f1e8e53 Merge remote-tracking branch 'origin/jaewon/fe-770-promote-demote-workspace-members-owners-settings-members' into jaewon/fe-991-e2e-integration-pass-for-billing-rework-v1-team-workspaces 2026-06-18 16:48:23 +09:00
dante01yoon
e1f251479c feat(workspace): back row-lock with Member.is_original_owner (FE-770) 2026-06-18 15:33:33 +09:00
AustinMroz
eafe2af91d Indicate in progress upload with spinner (#12673)
While a file is uploading to any of the file picker nodes (ie "Load
Image"), the 'select folder' icon is replaced with a loading spinner.

I had previously implemented this with a full progress bar indicating
the rate of upload, but found it particularly uninformative when used on
cloud.

<img width="659" height="494" alt="image"
src="https://github.com/user-attachments/assets/6d3ca82b-360b-44bc-b123-b276aae2c4d6"
/>
2026-06-17 18:09:49 -07:00
AustinMroz
4c870d84ed Fix disabling of linked widgets in props panel (#12896)
| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/9d602ee3-ff10-48b9-95ca-4c7f5ca57a45"
/> | <img width="360" alt="after"
src="https://github.com/user-attachments/assets/36e96aff-60ec-4f8c-b7c9-b4d68e03884c"
/>|

Making reactivity function is a little bit clunkier than I would like,
but it'll get simplified in the future by east coast swing
2026-06-17 18:05:57 -07:00
Maanil Verma
0c23e8305f fix: skip templates modal when opening a template from the URL (#12835)
## Summary

On first launch, the templates modal flashed open for a split second
before a deeplinked template (`?template=`) loaded, which felt broken.

## Changes

- **What**: Gate the first-launch templates modal on template URL
intent, alongside the existing shared-workflow (`?share=`) check. When a
template is being opened directly from the URL, the template modal no
longer opens. Behavior is unchanged when no template is in the URL — the
template modal still shows for first-time users.
- Test util: Added browser_tests/fixtures/utils/flashDetector.ts —
installs a pre-navigation requestAnimationFrame sampler that flags if a
[data-testid] element ever renders, even for a single frame. This
catches a brief flash that toBeHidden() (final-state only) cannot.

## Review Focus

`hasTemplateUrlIntent()` mirrors the existing
`hasSharedWorkflowIntent()` (direct `route.query` check plus
preserved-query fallback for the `/user-select` redirect path). Two
regression tests cover both the URL-param and preserved-intent cases.

**Coverage:**

- Unit (useWorkflowPersistenceV2.test.ts): the modal is not opened when
a template param is in the URL, and when template intent is preserved
across the /user-select redirect.
- E2E (templates.spec.ts): templates dialog never flashes when
first-time user opens a template link — verified red-without-fix,
green-with-fix.

Screen Recording 



https://github.com/user-attachments/assets/636094d4-0ef0-4e42-af32-d4e6c7ec5731



closes #12836
2026-06-17 21:00:02 +00:00
Alexander Brown
c408f39cee test: harden assets media-type filter spec against VirtualGrid flake (#12897)
## Summary

Harden the cloud assets media-type filter spec against a VirtualGrid
virtualization flake that intermittently failed CI at the
`waitForAssets(4)` precondition.

## Changes

- **What**: Replace the 7 `waitForAssets(MIXED_JOBS.length)`
preconditions in `assets-filter.spec.ts` with `waitForAssets()` (first
card visible = data loaded), and document why.

## Review Focus

CI artifact (`playwright-report-cloud`) from the failing run showed only
3 of 4 cards in the DOM — the 3D card was missing and the audio card
rendered taller (inline player). `VirtualGrid.vue` sizes its render
window from a single uniform `itemHeight` measured off the first card,
so the taller audio card pushes the 3D card out of the initial window
and it is virtualized out of the DOM until a re-measure (same cause as
#11635).

Requiring all 4 cards to be mounted simultaneously fights
virtualization. `tab.open()` already waits for the first card (data
loaded), and filtering reads the full asset store regardless of what is
mounted, so the per-filter count assertions still provide the real
coverage. No behavioral change to the tests — only the
readiness/precondition strategy.

Verified `pnpm exec eslint` and `pnpm typecheck:browser` pass. Cloud E2E
could not be run locally (needs a running frontend + backend); relies on
the cloud CI job for confirmation.

Related to #11635

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-06-17 19:38:22 +00:00
Alexander Brown
e994e4df58 refactor: store-backed WidgetId subgraph host widgets; delete widgetValueIO layer (#12617)
## Summary

Make `WidgetId` (`graphId:nodeId:name`) the single canonical widget
identity and represent subgraph promoted host widgets as ordinary
store-backed widgets addressed by it. This deletes two whole indirection
layers — the `world/*` widget-entity-IO layer and the
`PromotedWidgetView` runtime — leaving one model: a widget's data lives
in `widgetValueStore` keyed by `WidgetId`, and a `SubgraphNode` input
references it via `input.widgetId`.

**Net +304 lines across 107 files** (5,044 added / 4,740 deleted):
production code is net **−798** (1,521 added / 2,319 deleted) while
tests are net **+1,102** (3,523 added / 2,421 deleted). 10 files deleted
outright, 14 added.

## What got deleted

The old design wrapped every promoted subgraph widget in a synthetic
`IBaseWidget` "view" object with live getters that followed the source
widget, plus a manager to keep view identities stable, plus an IO
indirection layer over the store. All of it is gone:

- `promotedWidgetView.ts` — the `PromotedWidgetView` class (draw /
pointer / DOM-sync / projection / deepest-source resolution getters)
- `PromotedWidgetViewManager.ts` — view reconciliation/caching
- `world/widgetValueIO.ts` — the IO wrapper over `widgetValueStore`
- `world/entityIds.ts` + `world/brand.ts` — the `WidgetEntityId`
branded-id layer and the `entityId` field
- `widgetNodeTypeGuard.ts` — only used by the deleted view
- the per-`SubgraphNode` view machinery (`_promotedViewManager`,
`_cacheVersion`, view-key generation, DOM position-override cleanup) and
every now-dead `isPromotedWidgetView` branch across the panel, menu,
store, and util consumers
- `domWidgetStore` position-override APIs (`setPositionOverride` /
`clearPositionOverride`), only used to render a promoted DOM widget on a
different host node

## Why it's simpler

- One source of truth. A promoted host widget is `WidgetState` in the
store, seeded from the source at promotion (`registerWidget` with a
deep-cloned snapshot) and independent thereafter. No synthetic widget
objects, no runtime source-following, no view cache to invalidate.
- Resolution is data-driven. `resolveConcretePromotedWidget` walks
`SubgraphNode` inputs (`input.widgetId` + `resolveSubgraphInputTarget`)
instead of chasing view objects through `node.widgets`. This also
**fixes two-layer nested promotion** — the previously-skipped parity
test now passes and resolves through to the deepest concrete widget.
- The right-panel Parameters tab renders a subgraph node's promoted
widgets through the **same** store-backed path as ordinary node widgets:
display reads `WidgetState` via `widget.widgetId`, and value writes go
through `widgetValueStore.setValue(widgetId)`.

## Changes

- **What**:
- `WidgetId` branded type + `widgetId()` / `parseWidgetId()` /
`isWidgetId()` and a `WidgetState` type; `widgetValueStore` is
`WidgetId`-native (`registerWidget` / `getWidget` / `setValue` /
`deleteWidget`).
- Promotion creates host `WidgetState` then an input projection
(`input.widgetId`); demotion clears it; serialization and legacy
`proxyWidgets` migration round-trip through `input.widgetId`.
- `promotedInputWidget.ts` projects a store-backed ordinary widget from
an input slot; `SubgraphNode.widgets` is now a projected getter over
inputs (kept Litegraph-shaped so the canvas renderer and extensions
still read `node.widgets`). `invalidatePromotedViews()` is retained as a
no-op for extension compatibility.
- `promotedWidgetControl.ts` applies `control_after_generate` (e.g. seed
increment) on the host node, since the interior control widget is
link-fed and its value is dead; `syncPromotedComboHostOptions` mirrors
interior combo options onto host state.
- `multilineTextarea.ts` extracts the reusable multiline DOM-widget
behavior out of `useStringWidget` and adds promoted multiline
materialization via a `createPromotedHostWidget` app-layer hook (keeping
Litegraph core free of Vue/Pinia/DOM).
- Late-bound `LiteGraph` singleton holder (`litegraphInstance.ts`) to
break a widget-init import cycle.
- **Breaking**:
  - `IBaseWidget.entityId` removed — use `widgetId`.
- `SubgraphNode.widgets` no longer exposes the old `PromotedWidgetView`
objects; promoted state lives in `widgetValueStore` keyed by
`input.widgetId` and `widgets` is a projection of inputs. The
`widget-promoted` event now carries the concrete interior widget.
Extension code reading `entityId` or relying on `PromotedWidgetView` is
affected.

## Review Focus

- Promotion / demotion / serialization round-tripping through
`input.widgetId` + `widgetValueStore`, incl. the legacy `proxyWidgets`
migration.
- Snapshot-at-promotion semantics (host widget does not follow the
source after creation), and the combo-options exception via
`syncPromotedComboHostOptions`.
- Two-layer nested resolution in `resolveConcretePromotedWidget` +
`SubgraphNode` nested-source resolution.
- The unified Parameters tab (`TabSubgraphInputs` → `SectionWidgets`):
value edit / rename / favorite / hide / reorder for promoted inputs are
wired through the store but warrant a visual/e2e pass.
- Litegraph-compat seams worth a careful read: projected
`SubgraphNode.widgets`, the canvas-edit `callback` bridge back to the
store, host-level `control_after_generate`, and the late-bound
`LiteGraph` holder / `domWidget.ts` import ordering.

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: AustinMroz <austin@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
2026-06-16 23:19:12 +00:00
AustinMroz
cb52a3821b fix groups dragging children with control held (#12867)
When control is held, an active drag operation should cease applying
movements to nodes contained by selected groups. This functionality was
broken in vue mode because of unnecessary reimplementation of the code
for calculating items contained by groups during drag operations
2026-06-16 22:32:31 +00:00
jaeone94
941f220582 fix: bind replacement node widgets to reused id (#12872)
## Summary

Fixes a Nodes 2.0 node replacement regression where widgets that only
exist on the replacement node were not registered with the widget value
store, causing their Vue-rendered controls to fall back to component
defaults such as `0` instead of the replacement node's real widget
default.

The root cause is that `replaceWithMapping()` replaces the placeholder
node in-place by writing directly to `graph._nodes` and
`graph._nodes_by_id`. That path intentionally preserves the old node id,
but it also bypasses the normal `LGraph.add()` flow that binds widgets
to their owning node id. As a result, newly introduced bindable widgets
on the replacement node could exist on the LiteGraph node object while
remaining absent from `useWidgetValueStore`, which is the state Vue
Nodes reads from when rendering widget controls.

## Changes

- **What**: Bind every bindable widget on the replacement node to the
reused node id inside `replaceWithMapping()` after the replacement node
is inserted into the graph maps and before widget values are
transferred.
- **What**: Preserve the existing widget value transfer behavior for
mapped widgets. Because widgets are now bound before `newWidget.value =
oldValue` runs, transferred values are written through the normal widget
store state instead of only mutating the unbound widget object.
- **What**: Add a focused unit regression check that verifies
replacement-only widgets are bound with the reused node id during node
replacement.
- **What**: Extend the existing node replacement Playwright coverage to
assert the Vue Nodes rendered input for `KSampler.denoise` keeps the
expected replacement value after the replacement flow.
- **Breaking**: None.
- **Dependencies**: None.

## Review Focus

Please focus on the placement of the widget binding in
`replaceWithMapping()`. The binding happens after the new node has been
assigned the reused id and inserted into the graph's node maps, but
before mapped widget values are copied over from the old node. This
mirrors the important part of the normal graph add flow for widgets
while keeping the in-place replacement behavior intact.

The tests intentionally avoid asserting replacement-node fixture
defaults in isolation. The unit test verifies the actual new side effect
that prevents the regression: `setNodeId()` is called for a bindable
widget that was not present on the old node. The Playwright assertion
then covers the user-visible Nodes 2.0 symptom: the replacement widget
is rendered from the widget store instead of falling back to the Vue
numeric default.

Linear: FE-1070

## Validation

- `pnpm vitest run
src/platform/nodeReplacement/useNodeReplacement.test.ts`
- `pnpm typecheck:browser`
- `PLAYWRIGHT_LOCAL=1 PLAYWRIGHT_TEST_URL=http://localhost:5174 pnpm
exec playwright test --project=chromium
browser_tests/tests/nodeReplacement.spec.ts -g "Widget values are
preserved after replacement"`
- `pnpm lint`
- `pnpm typecheck`
- Commit hook also reran staged formatting/linting and `pnpm typecheck`
during the final amend.

## Screenshots (if applicable)

Before 


https://github.com/user-attachments/assets/dc4e8137-d8aa-4a70-9973-5559ed84b90e

After


https://github.com/user-attachments/assets/4c70b9e4-d971-4e94-8d2f-12b0f2b00a09
2026-06-16 17:11:43 +00:00
Dante
75553fc214 fix(settings): widen the Settings dialog to 1280 (#12849)
## Summary

The redesigned Settings dialog (Figma DES `3253-16079`) is **1280px**
wide, but it rendered at **960px**.

Root cause — the width was capped at 960 in **two** layers:
1. `useSettingsDialog.ts` → `SETTINGS_CONTENT_CLASS` (`max-w-[960px]`)
sizes the Reka dialog shell.
2. `SettingDialog.vue` → `<BaseModalLayout size="sm">` (`SIZE_CLASSES.sm
= max-w-[960px]`) sizes the modal content.

Widening only the shell leaves the inner `BaseModalLayout` at 960 (empty
space on the right). This sets both to **1280px** and lets
`BaseModalLayout` fill the shell (`size="full"`).

The dialog size is **not** a workspace-specific concern, so it applies
to all Settings (OSS + cloud) — no feature-flag gate.

Found during FE-768 designer QA.

## Verification

- Live: dialog measures 1280px, content area 1006px (was 960 / 688).
- `useSettingsDialog.test.ts`: `contentClass` is 1280px (`size:
'full'`).
- `pnpm typecheck` / `lint` / `format` / unit tests green.

## Test Plan

- [x] Settings dialog renders at 1280px with the content filling the
dialog
- [x] Unit test asserts the 1280px sizing

## Screenshots

Settings ▸ Plan & Credits at **1280px** (content fills the dialog; was
960px shell / 688px content area):

**Personal — Pro:**

<img width="720" alt="Settings dialog at 1280px — personal Pro"
src="https://github.com/user-attachments/assets/adc2fd9f-d249-469f-b947-1ec8f674cbb0"
/>

**Team:**

<img width="720" alt="Settings dialog at 1280px — team"
src="https://github.com/user-attachments/assets/e7378067-11a2-411b-b37b-98c8aecb82b1"
/>

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-06-16 13:03:02 +00:00
Alexander Brown
7438f004c1 test: add mask editor load/save round-trip browser tests (#11369)
*PR Created by the Glary-Bot Agent*

---

## Summary

Adds `browser_tests/tests/maskEditorLoadSave.spec.ts` covering the
untested image loading, save round-trip, canvas dimension verification,
and error handling paths in the mask editor.

### Coverage gaps filled
- `useImageLoader.ts` — image loads onto canvas with correct dimensions
- `useMaskEditorSaver.ts` — save uploads non-empty mask data, round-trip
preserves state
- `useMaskEditorLoader.ts` — editor initialization, canvas dimension
matching
- Error handling — partial upload failure keeps dialog open

### Test cases (5 tests, 2 groups)
| Group | Tests | Behavior |
|---|---|---|
| Save round-trip | 3 | Save with drawn mask uploads non-empty data,
save-and-reopen preserves mask state, canvas dimensions match loaded
image |
| Load and error handling | 2 | Opening editor loads image onto canvas,
partial upload failure keeps dialog open |

### References
- Reuses patterns from existing `maskEditor.spec.ts` (`loadImageOnNode`,
`openMaskEditorDialog`, `getMaskCanvasPixelData`,
`drawStrokeOnPointerZone`, route mocking for upload endpoints)
- Follows `browser_tests/AGENTS.md` directory structure
- Follows `browser_tests/FLAKE_PREVENTION_RULES.md` assertion patterns

### Verification
- TypeScript: clean
- ESLint: clean
- oxlint: clean
- oxfmt: formatted

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11369-test-add-mask-editor-load-save-round-trip-browser-tests-3466d73d3650818b8245c0b355011136)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: bymyself <cbyrne@comfy.org>
2026-06-16 01:41:18 +00:00
AustinMroz
cdde1248d4 Resolve errant executionIds on workflow restore (#12659)
Node previews are stored by `locatorId`, but sent from the server by
`executionId`. Normally, this difference is reconciled when the event is
received, but this step is skipped when the workflow is backgrounded.
Upon reloading the workflow, these backlogged `executionId`s were
incorrectly mapped directly onto node outputs. Any outputs located
inside a subgraph would then fail to display because `executionId`s are
now `locatorId`s.

This is solved by resolving any `executionId`s at time of output
restoration. Because `executionId`s can only leak into the outputs of
backgrounded workflows, it is safe for resolved `executionId`s to
overwrite any pre-existing `locatorId`s.

It might wind up cleaner to instead properly enforce that the
nodeOutputs cached by change tracker resolve a `locatorId` at time of
receipt. This would follow naturally for properly branded id types, but
would then require resolving `locatorId` from suspended workflows which
is a good bit more involved.
2026-06-15 21:29:07 +00:00
Dante
4b979f4ad0 feat(dialog): migrate mask editor + 3D viewer dialogs to the Reka renderer (FE-578) (6a -1) (#12848)
## Summary

Splits the **heavy, hard-to-test surface** out of the Phase 6 dialog
cutover (#12593) into its own independently reviewable, independently
testable PR — per @jtydhr88's review feedback that #12593 bundled too
many concepts (3D, mask editor, and the renderer cutover) to test
thoroughly at once.

This PR migrates only the four style-string dialog callers that carry
**Playwright screenshot baselines** and **maximize behavior** — the mask
editor and the 3D viewers — plus the shared dialog infrastructure they
need. **#12593 is rebased on top of this PR** and now contains only the
renderer cutover.

Parent:
[FE-571](https://linear.app/comfyorg/issue/FE-571/dialog-system-migration-primevue-reka-ui-parent)
This phase:
[FE-578](https://linear.app/comfyorg/issue/FE-578/phase-6-remove-primevue-dialogconfirmdialog-imports-clean-up-css)

## Why this is safe to land alone

**The global renderer default stays `'primevue'`.** Every caller
migrated here sets `renderer: 'reka'` explicitly, and the infra
additions are purely additive. So no other dialog changes behavior and
there is no half-migrated state — the default flip and the remaining
caller migrations all live in the stacked cutover (#12593).

## Changes

**Heavy callers → `renderer: 'reka'` + `size`/`contentClass`:**
- Mask editor (`useMaskEditor.ts`) — `mask-editor-dialog` hook class
moves to `contentClass` so `browser_tests` selectors keep working
unchanged
- 3D viewers ×4 (`ViewerControls.vue`, `AssetsSidebarTab.vue`,
`JobHistorySidebarTab.vue`, `load3d.ts`)

**Infra to reach Reka parity (additive):**
- `dialogStore`: `headerClass`/`bodyClass`/`footerClass` (Reka-path
analogues of `pt.header`/`pt.content`/`pt.footer`)
- `GlobalDialog`: forward the section classes; merge `bodyClass` into
the body wrapper
- `DialogContent`: maximized re-asserts its dimension classes after the
caller's `contentClass` so maximize wins, mirroring
`.p-dialog-maximized` `!important`
- `tailwind-utils`: teach tailwind-merge the `max-h-none` class so
maximize can release the caller's `max-height`
- `rekaPrimeVueBridge`: keep a backgrounded reka dialog from dismissing
when a stacked dialog opens on top of it
- `maskeditor/useKeyboard`: capture keydown so undo/redo survive the
Reka focus trap

## Quality gates

- [x] `pnpm typecheck` — clean
- [x] `pnpm lint` / `pnpm format` — clean (lint-staged)
- [x] `GlobalDialog.test.ts` — 25 passing (incl. new section-class +
maximize-override + stacked-dismiss tests)
- [x] Changed-source unit tests (`useMaskEditor`, `useKeyboard`,
`ViewerControls`, `load3d`) — 77 passing
- [ ] CI Playwright — mask editor baselines refreshed for the Reka
chrome (`browser_tests/tests/maskEditor.spec.ts-snapshots/*`)

## Out of scope (stacked in #12593)

The renderer cutover: `showConfirmDialog` flip, remaining
`dialogService`/composable callers (signin, top-up, workspace,
subscription, publish, share, …), **the `createDialog` default flip to
`'reka'`**, e2e selector retargeting, and the `ConfirmationService`
removal. PrimeVue branch deletion remains Phase 6b.


## 📸 Screenshots — before (PrimeVue) → after (Reka)

Captured via Chrome DevTools against this branch in cloud mode
(`cloud.comfy.org` backend), with an input image / `cube.obj` loaded.
Only the dialog **chrome** migrates (PrimeVue `Dialog` → Reka
`DialogContent`); the editor/viewer content is unchanged.

### Mask editor (`useMaskEditor`)
| Before (PrimeVue) | After (Reka) |
|---|---|
| <img width="430" alt="mask editor before"
src="https://github.com/user-attachments/assets/267e63b5-0832-409e-9c41-edf5ff96561f"
/> | <img width="430" alt="mask editor after"
src="https://github.com/user-attachments/assets/073cd824-8b01-4c07-99e1-a3a054906c7a"
/> |

### 3D viewer (`load3d` / `ViewerControls`)
| Before (PrimeVue) | After (Reka) |
|---|---|
| <img width="430" alt="3D viewer before"
src="https://github.com/user-attachments/assets/17b2cd2f-18e4-4d9a-9e0e-80ef833db216"
/> | <img width="430" alt="3D viewer after"
src="https://github.com/user-attachments/assets/9e20a7a5-4d22-40e6-8fa2-ece58b6e4d20"
/> |

### 3D viewer — maximized (maximize-wins dimension re-assertion in
`DialogContent`)
| Before (PrimeVue) | After (Reka) |
|---|---|
| <img width="430" alt="3D viewer maximized before"
src="https://github.com/user-attachments/assets/b705a4d5-4657-41ad-b6f3-95e54494ac9b"
/> | <img width="430" alt="3D viewer maximized after"
src="https://github.com/user-attachments/assets/188de427-ab58-45a9-8666-967b2908c320"
/> |
2026-06-15 13:13:03 +00:00
Dante
700ff4644f feat(workspace): switcher popover left of profile menu + DES-246 copy (FE-769) (#12763)
## Summary

Aligns the workspace switcher and creation flow to DES-246 (FE-769): the
switcher popover now opens to the **left** of the profile menu instead
of on top of it, team workspace rows drop the tier badge, and the
create-workspace dialog matches the design's copy and surface.

## Changes

- **What**:
- `CurrentUserPopoverWorkspace.vue`: replace the nested PrimeVue
`Popover` (rendered on top of the menu) with an inline panel anchored
left of the selector row (`right-full`, top-aligned, outside-click close
via VueUse)
- `WorkspaceSwitcherPopover.vue`: tier badge only renders on the
personal workspace row ("Remove the tier badge for team workspaces,
since there'll only be one plan now")
- Copy (`en/main.json`): switcher create label "Create a team workspace"
("Explicitly say 'team'"); create dialog message "Workspaces keep your
projects and files organized. Subscribe to a Team plan to invite
members.", label "Workspace name", placeholder "Ex: Comfy Org"
- `CreateWorkspaceDialogContent.vue`: surface matched to the Create
Workspace / Default frame — 512px width, muted name label, filled 40px
TextInput (`bg-secondary-background`, `rounded-lg`, `px-4`)
- Invite-flow copy deltas from DES-246: none — #12759 (FE-768) already
matches the design verbatim

Was stacked on #12762 (FE-778); that PR merged, so this is now rebased
onto `main` with only the FE-769 commits.

- Fixes
[FE-769](https://linear.app/comfyorg/issue/FE-769/updates-to-misc-ux)

## Review Focus

- The switcher panel now lives inside the profile popover DOM (no
teleport): clicking a row keeps the menu open, outside-click closes only
the panel
- The CREATOR badge on the profile-menu workspace selector row (visible
in the Figma frame) is intentionally not included — it needs
`is_original_owner` from the BE role-change work and ships with FE-770
- `leave-last-workspace -> auto-create` flow is deferred (not V1),
intentionally untouched

## Screenshots (if applicable)

| Before (overlaps menu, tier badges, "Create new workspace") | After
(left of menu, no team badges, "Create a team workspace") |
| --- | --- |
| <img width="700" alt="before"
src="https://github.com/user-attachments/assets/5522fcca-91b5-49e6-beaa-df1b88bed018"
/> | <img width="1100" alt="after"
src="https://github.com/user-attachments/assets/ce74d42e-19bd-4fe6-9477-b22e5964736d"
/> |

Create-workspace dialog with DES-246 copy and surface (512px, filled
input):

<img width="900" alt="create dialog after"
src="https://github.com/user-attachments/assets/e78eff0a-1c0e-4bbb-ac70-6cc1da996682"
/>
2026-06-15 12:41:14 +00:00
jaeone94
6d43320b93 Simplify missing model error presentation (#12793)
## Summary

Simplifies the Missing Models error card as the fifth slice of the
catalog-driven error-tab redesign. This PR is intentionally larger than
the previous slices because Missing Models is the only remaining error
type where the card UI, OSS download flow, Cloud import flow, and shared
model-import dialog all have to move together to preserve the resolution
path.

The high-level goal is to make Missing Models behave like the other
simplified error cards: show the exact missing item, show the affected
nodes, keep locate actions predictable, and only expose actions that can
actually resolve the problem.

This follows the staged error-tab cleanup plan:

1. #12683 refined validation, runtime, and prompt error presentation.
2. #12705 simplified missing media error presentation.
3. #12735 simplified missing node pack error presentation.
4. #12768 simplified swap node error presentation.
5. This PR simplifies missing model presentation and the model-import
handoff.

## Why This PR Is Larger

Missing Models has more resolution paths than the previous error groups:

- OSS can refresh model state, download individual models, and download
all available models.
- Cloud cannot download directly from the panel; it resolves supported
rows through the model import dialog.
- Some Cloud rows cannot be resolved through import at all because the
node/widget cannot consume imported model assets.
- Importing from Cloud needs to know the originating missing-model row
so it can lock the expected model type and apply the imported model back
to the affected widgets.
- Already-imported files can still be unusable if they were imported
under a different model type than the missing node expects.

Because of those constraints, splitting the card layout from the dialog
handoff would leave either a misleading Import button or an import
dialog that does not know what it is resolving. This PR keeps that
behavior in one reviewable unit.

## User-Facing Behavior

### Shared Missing Models Card

- Replaces the older grouped presentation with compact model rows.
- Shows each missing model as the primary row label.
- Shows model metadata as a smaller sublabel instead of using large
section headers.
- Keeps locate-node controls visually consistent with the other
simplified error cards.
- Keeps rows expandable when multiple nodes reference the same missing
model.
- Shows the affected node rows under expanded models.
- Allows single-reference rows to locate the affected node without
rendering an extra duplicate child row.
- Keeps unknown rows visible, including their affected nodes, instead of
silently hiding them.
- Removes the old library-select UI from the Missing Models card.

### OSS Behavior

- Keeps the refresh action available from the Missing Models group
header.
- Keeps individual Download actions for downloadable models.
- Moves file size out of the Download button label and into the row
sublabel.
- Keeps Download all when multiple downloadable models are available.
- Places Download all at the bottom of the card rather than competing
with the group header.
- Leaves rows without a download URL as non-downloadable instead of
rendering a broken action.

### Cloud Behavior

- Shows Import only for missing models that can be resolved by importing
a model asset of the required type.
- Separates models that cannot be resolved through Cloud import into an
Import Not Supported section.
- Gives unsupported rows a direct explanation: nodes referencing those
models do not support imported models, so users need to open the node
and choose a supported built-in model or replace the node with a
supported loader.
- Treats unknown model type/directory as unsupported for Cloud import,
because the import dialog cannot lock a valid model type and the node
cannot safely consume the imported asset.
- Keeps affected nodes visible in the unsupported section so users still
have a path to locate and replace the node manually.

## Cloud Import Dialog Changes

The shared model import dialog now accepts missing-model context when
opened from the Missing Models card.

When that context is present:

- The dialog shows which missing model will be replaced.
- The dialog lists the affected node/widget references that will be
updated.
- The model type selector is locked to the required model
directory/type.
- The Back/import-another path is disabled when it would break the
targeted missing-model flow.
- Import progress can be associated with the originating missing-model
row.
- After import completion, matching missing-model references are applied
automatically where possible.
- If the selected file is already imported under an incompatible model
type, the dialog shows a targeted failure state explaining why this
import cannot resolve the missing model.

This keeps the generic import dialog reusable while adding only the
context-specific behavior needed for Missing Models.

## Implementation Notes

- `MissingModelCard.vue` owns the card-level grouping and OSS/Cloud
section decisions.
- `MissingModelRow.vue` owns per-model row rendering, expansion, locate
actions, import/download actions, and row-level progress states.
- `useMissingModelInteractions.ts` remains the interaction layer for
locating nodes and applying resolved model selections.
- `UploadModelDialog.vue`, `UploadModelConfirmation.vue`,
`UploadModelFooter.vue`, `UploadModelProgress.vue`, and
`useUploadModelWizard.ts` receive the missing-model context needed by
the Cloud import handoff.
- `MissingModelLibrarySelect.vue` is removed because the simplified card
no longer exposes that inline selection path.
- Locale and selector changes are limited to the new simplified
row/section states and removed unused Missing Models strings.

## Tests Added / Updated

- Unit coverage for Missing Models card grouping and row states.
- Unit coverage for importable vs unsupported Cloud rows.
- Unit coverage for model row expansion, locate actions, progress
display, and action availability.
- Unit coverage for upload confirmation/footer/progress behavior when a
missing-model context is present.
- Unit coverage for incompatible already-imported model handling.
- E2E coverage for OSS Missing Models presentation.
- E2E coverage for mode-aware Missing Models interactions.
- Cloud E2E coverage for importable rows vs Import Not Supported rows.
- Cloud E2E coverage for opening the import dialog with missing-model
replacement context.

## Review Focus

- Cloud import eligibility: unsupported or unknown model rows should not
expose Import as if the row can be resolved automatically.
- Missing-model context in the import dialog: the required model type
should be locked, and the affected node/widget references should be
clear.
- OSS parity: OSS should keep refresh, individual Download, and Download
all while visually matching the simplified Cloud card where possible.
- Narrow side panel behavior: row labels may wrap, but link, primary
action, and locate controls should not overlap.
- Scope boundaries: this PR intentionally does not redesign Missing Node
Pack / Swap Node / Missing Media again; visual parity issues shared
across those cards can be handled in a follow-up unification pass if
needed.

## Validation

- `pnpm format`
- `pnpm lint`
- `pnpm typecheck`
- Related unit tests: 8 files / 84 tests passed
- `pnpm build`
- OSS Missing Models E2E: `errorsTabMissingModels.spec.ts` passed, 8/8
- Mode-aware Missing Models E2E subset passed, 11/11, excluding
unrelated local paste clipboard cases
- `pnpm build:cloud`
- Cloud Missing Models E2E: `errorsTabCloudMissingModels.spec.ts`
passed, 3/3
- Final Claude review: no Blocker or Major findings

## Breaking / Dependencies

- Breaking: none.
- Dependencies: none.

## Screenshots
OSS
<img width="575" height="393" alt="스크린샷 2026-06-12 오전 12 25 27"
src="https://github.com/user-attachments/assets/f5c44f95-711a-4d3d-99bd-f39ac2bb2012"
/>
<img width="659" height="351" alt="스크린샷 2026-06-12 오전 12 24 37"
src="https://github.com/user-attachments/assets/4bb65a47-c1aa-408b-836b-a1998412f815"
/>

Cloud

<img width="688" height="357" alt="스크린샷 2026-06-12 오전 12 23 59"
src="https://github.com/user-attachments/assets/9330a7e7-9f22-420f-82b3-dde0fb2b3dd1"
/>
<img width="531" height="437" alt="스크린샷 2026-06-12 오전 12 21 13"
src="https://github.com/user-attachments/assets/734bd911-f6f7-4872-8868-bb927ddeedd8"
/>

New import model flow



https://github.com/user-attachments/assets/c094c670-62b9-47ce-bfe1-2d09f4f7359d
2026-06-15 12:17:31 +00:00
Dante
f8993d2279 Merge branch 'main' into jaewon/fe-878-bug-subscription-required-error-message-feels-choppyabrupt 2026-06-15 15:44:28 +09:00
dante01yoon
dc6c48c6dd fix(billing): route queue paywall (402) to modal, out of error panel (FE-878)
The free-tier queue paywall arrives on the POST /prompt path as { type: PAYMENT_REQUIRED, message: "Subscription required to queue workflows" }, which the execution_error-only routing did not cover, so it still surfaced as a raw error in the panel. Teach the runtime matcher this message and short-circuit the queuePrompt catch to open the precondition modal, keeping it out of the error panel and error count. Add an e2e regression test for the queue paywall plus a control for ordinary queue errors.

Fixes #12840
2026-06-15 12:36:23 +09:00
AustinMroz
e138d17459 Fix themeing of nodes (#12712)
In moving styling to `documentElement`, #9516 introduced a regression
preventing themes from styling nodes.
| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/0a27ea1b-ff15-4524-b491-a0de9fee6ed2"
/> | <img width="360" alt="after"
src="https://github.com/user-attachments/assets/36aee446-6b7d-4b05-96de-39b19989af0d"
/>|

Note: Some elements (like the app mode toggle) are themed using
different variables (`--secondary-background` instead of
`--component-node-background`). I think the sanest approach is to define
`--secondary-background` to be `--component-node-background` by default,
but that feels better handled as a followup PR

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: github-actions <github-actions@github.com>
2026-06-13 02:40:58 +00:00
pythongosssss
193f23e8c2 Revert "feat: default search to essentials when graph is empty" (#12814)
Reverts Comfy-Org/ComfyUI_frontend#12377
2026-06-12 18:18:34 +00:00
AustinMroz
46526cfabd On mode toggle apply to group children (#12809)
When performing mode toggle operations (like bypass or mute) with a
group (the colored rectangles) selected, nodes contained within the
group will be considered selected and will have their state toggled.
<img width="1024" height="1024" alt="AnimateDiff_00002"
src="https://github.com/user-attachments/assets/c4e9db17-3fe8-4fd8-9012-0e9a0bc59707"
/>
2026-06-12 02:44:02 +00:00
dante01yoon
a2b84ec170 Merge remote-tracking branch 'origin/jaewon/fe-769-updates-to-misc-ux' into jaewon/fe-991-e2e-integration-pass-for-billing-rework-v1-team-workspaces 2026-06-11 22:35:15 +09:00
dante01yoon
ac3e816549 Merge remote-tracking branch 'origin/jaewon/fe-770-promote-demote-workspace-members-owners-settings-members' into jaewon/fe-991-e2e-integration-pass-for-billing-rework-v1-team-workspaces 2026-06-11 22:34:55 +09:00
dante01yoon
85eb4ea741 Merge remote-tracking branch 'origin/jaewon/fe-964-build-unified-credits-tile-settings-plan-credits-on-the' into jaewon/fe-991-e2e-integration-pass-for-billing-rework-v1-team-workspaces 2026-06-11 22:34:39 +09:00
dante01yoon
2ca2360216 Merge remote-tracking branch 'origin/jaewon/fe-904-b2-complete-the-billing-facade-resubscribe-topup-status' into jaewon/fe-933-b3-repoint-direct-bypass-billing-consumers-to-the-facade
# Conflicts:
#	src/platform/telemetry/providers/cloud/PostHogTelemetryProvider.test.ts
2026-06-11 18:34:38 +09:00
dante01yoon
4b7a51ec29 test(workspace): e2e for member role change user stories (FE-770)
Covers the Members-tab role flows against a fully mocked cloud boot
(FE-964 creditsTile pattern): creator/self rows hide the row menu while
member rows expose Change role + Remove member; promote shows the
Make-owner dialog (Figma copy), PATCHes the proposed role contract,
flips the Role column and re-sorts under the creator; the promoted
owner demotes back through the Demote dialog; same-role picks and
Cancel are no-ops; a failed PATCH keeps the dialog open with an error
toast. Reka submenus don't open on Playwright's synthetic hover, so the
subtrigger is driven with ArrowRight.
2026-06-11 17:31:38 +09:00
Dante
391f9e464a test(billing): e2e regression for facade-driven billing surfaces (FE-933) (#12760)
## What
E2E regression coverage for the FE-933 (B3) facade repoints — closes the
browser_tests gap flagged on #12643.

> **Stacked on #12643** (base = the FE-933 branch). Retarget after it
merges.

## Coverage
`browser_tests/tests/billingFacadeConsumers.spec.ts` (`@cloud` project)
boots the cloud app against fully mocked endpoints (same raw-page
pattern as `creditsTile.spec.ts`):

- **Avatar popover (PRO, personal)** — tier badge "Pro", formatted
balance `12,660`, add-credits action all render from `useBillingContext`
→ legacy `/customers/*` adapter.
- **Free-tier dialog renewal date (T5)** — boots with
`subscription_required` + `team_workspaces_enabled` (production shape)
and an inactive FREE user; the subscription extension's
`requireActiveSubscription()` opens the free-tier dialog at boot and the
"Your credits refresh on Feb 20, 2099." line renders from the facade's
raw ISO `renewalDate`.

Mocks are typed (`CloudSubscriptionStatusResponse`, `RemoteConfig`);
renewal date uses 10:00Z so the en-US calendar date is timezone-stable
in CI.

## Verification
Local run against a real `pnpm build:cloud` dist served statically:
```
pnpm exec playwright test --project=cloud billingFacadeConsumers
  2 passed (3.0s)
```
typecheck / eslint / oxfmt clean.
2026-06-11 14:02:01 +09:00
jaeone94
c7797b201e Simplify swap node error presentation (#12768)
## Summary

Simplifies the Swap Nodes error card as the fourth slice of the
catalog/error-tab presentation refactor, aligning it with the newer
compact error-row patterns while preserving the existing replace and
locate behavior.

This follows the staged rollout plan from the earlier error-tab PRs:

1. #12683 refined execution-style errors: validation, runtime, and
prompt errors.
2. #12705 simplified missing media errors into flat, locatable rows.
3. #12735 simplified missing node pack errors and aligned grouped-row
behavior.
4. This PR applies the same simplification pass to Swap Nodes errors.
5. A later PR is expected to handle Missing Models, which is larger and
intentionally kept separate.

After the Missing Models slice lands, a follow-up consistency PR will
normalize the shared row/disclosure pattern across Missing Node Packs,
Swap Nodes, and Missing Models together. That follow-up will cover
parameterized i18n labels for disclosure controls, shared text-button
styling, and consistent disclosure semantics/accessibility across those
grouped rows.

## Changes

- **What**: Reworks the Swap Nodes card rows so each replacement group
is presented as a compact row with the source node type, replacement
target, replace action, and locate action.
- **What**: For a single affected node, the visible row label can be
clicked to locate the node, matching the interaction model used by the
newer missing-media and missing-node rows.
- **What**: For multiple affected nodes with the same replacement
target, the group renders a count badge and a disclosure row. Expanding
the group shows the affected node rows, each with its own locate action.
- **What**: Removes the old node-id badge path from Swap Nodes rows.
Node-id badges remain available to the other error cards that still own
that behavior.
- **What**: Keeps replacement behavior unchanged: per-group replacement
and replace-all still call through the existing node replacement store
flow.
- **What**: Adds regression coverage for the new grouped-row UI,
including same-type grouping in both Vue Nodes and LiteGraph render
modes.
- **Breaking**: None.
- **Dependencies**: None.

## Review Focus

Please focus on the Swap Nodes presentation and interaction symmetry
with the previous error-tab PRs:

- Single-node groups should remain directly locatable via the row label
and the locate icon.
- Multi-node groups should expose the count and expand/collapse behavior
without adding duplicate focusable disclosure controls.
- The visible row labels intentionally keep their own accessible names,
while the separate locate icon uses the generic `Locate node on canvas`
accessible name. This mirrors the established pattern from the previous
slices.
- The newly added Playwright fixture covers two same-type replaceable
nodes so duplicate group keys and grouped disclosure behavior are
exercised end-to-end.

## Validation

- `pnpm format`
- `pnpm test:unit
src/platform/nodeReplacement/components/SwapNodeGroupRow.test.ts`
- `pnpm test:browser:local browser_tests/tests/nodeReplacement.spec.ts
--project=chromium`
- Pre-commit hook: lint-staged, stylelint, oxfmt, oxlint, eslint, `pnpm
typecheck`, `pnpm typecheck:browser`
- Pre-push hook: `pnpm knip --cache`
- Additional parallel code review pass completed locally; no blocker or
major issues remained.

## Screenshots (if applicable)

This PR 

<img width="561" height="362" alt="스크린샷 2026-06-11 오전 3 46 06"
src="https://github.com/user-attachments/assets/65395467-6c2f-4aa1-84c5-3d9614c00c80"
/>

old (Main)
<img width="611" height="798" alt="스크린샷 2026-06-11 오전 3 46 32"
src="https://github.com/user-attachments/assets/3862d5df-f839-40c0-9488-ce64b051378e"
/>
2026-06-11 03:50:16 +00:00
dante01yoon
992da60ba7 feat(billing): add DES-247 empty states to the CreditsTile (FE-964) 2026-06-11 12:14:14 +09:00
dante01yoon
b80768fe90 test(workspace): cover switcher placement, badge removal, DES-246 copy 2026-06-11 09:58:08 +09:00
Dante
598cf33ab7 [bugfix] Truncate long workspace names in workspace switcher (#12762)
## Summary

Long team workspace names wrapped onto multiple lines in the user-menu
workspace switcher, overflowing the fixed 54px rows and breaking the
dropdown layout. Applies the same single-line ellipsis pattern already
used by the current-workspace header
(`CurrentUserPopoverWorkspace.vue`).

## Changes

- **What**: `truncate` on the switcher name span, `max-w-full` on the
name row, `shrink-0` on avatar/tier badge/check icon so only the name
shrinks (`WorkspaceSwitcherPopover.vue`, 5 lines)
- Regression tests: Vitest component test + `@cloud` Playwright e2e
measuring single-line render height

Fixes
[FE-778](https://linear.app/comfyorg/issue/FE-778/bug-team-workspace-names-wrapping-to-multiple-lines-display-poorly-in)

## Red-Green Verification

| Commit | CI | Result |
|---|---|---|
| `30e04e2` test only | [Tests
Unit](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/27278378157)
/ [Tests
E2E](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/27278378213)
| 🔴 new unit test + cloud e2e fail (proves tests catch the
bug) |
| `d8f9a5c` fix | [Tests
Unit](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/27279508881)
/ [Tests
E2E](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/27279508715)
| 🟢 same tests pass |

## Screenshots

| Before | After |
|---|---|
| <img width="320" alt="before — name wraps to 4 lines, rows collide"
src="https://github.com/user-attachments/assets/90f3286a-5b50-4477-9b5c-9d32d0b026e4"
/> | <img width="320" alt="after — single line with ellipsis, row height
intact"
src="https://github.com/user-attachments/assets/8e47bbb2-b5b1-4945-a008-68491f39dc46"
/> |

## Review Focus

- Truncation chain: the name span is a flex item, so `truncate`
(overflow-hidden) zeroes its automatic min size; `max-w-full` caps the
`items-start` row at the container width. Mirrors the header pattern —
no new component.
- Figma `Team Plan - Workspaces` (Workspaces Menu component, node
2045-14413) specifies compact single-line rows; long-name overflow was
undesigned, truncation preserves the spec'd layout.
2026-06-10 14:45:36 +00:00
dante01yoon
26be182ff3 test(billing): enable workspaces flag via remote config in cloud e2e
The cloud CI project runs a production build, which ignores the dev-only
ff: localStorage override — the Workspace settings panel never appeared
and the spec timed out on every run. Resolve the flag the way production
does: mock /api/features (the remote-config source) with
team_workspaces_enabled instead.
2026-06-10 19:02:39 +09:00
jaeone94
1b14f4df8a Simplify missing node pack error presentation (#12735)
## Summary

Simplify the Missing Node Packs error card so it follows the new
error-tab item-row direction, with clearer pack rows, predictable locate
behavior, and focused E2E coverage.

This is the third PR in the staged error-tab simplification plan:

1. Merged: execution/prompt/validation error presentation and catalog
grouping in #12683.
2. Merged: missing media presentation simplification in #12705.
3. This PR: missing node pack presentation simplification.
4. Planned next: swap-node presentation simplification.
5. Planned later: missing model presentation and action-flow
simplification.

## Changes

- **What**: Refactors Missing Node Packs rows so pack-level and
node-level actions are easier to scan and more consistent with the rest
of the refreshed Errors tab.
- **What**: Removes the node-id badge from missing node pack rows,
matching the simplified item-row direction.
- **What**: Makes a single-node known pack row directly locatable from
the pack label, rather than rendering an extra child row.
- **What**: Keeps multi-node packs collapsed by default, with both the
chevron and pack title toggling the child node list.
- **What**: Keeps unknown packs expanded by default, including the
single-node unknown-pack case, so users can still see the unresolved
node type immediately.
- **What**: Keeps per-node child rows clickable for locate-on-canvas
behavior when a pack contains multiple affected nodes.
- **What**: Replaces missing-node-pack action labels with shared
`g.install` and `g.search` copy and removes now-unused English locale
keys.
- **What**: Adds targeted Playwright coverage for the simplified
missing-node-pack card, including unknown-pack default rows, row-label
locate behavior, and chevron/title expansion behavior.
- **Breaking**: None.
- **Dependencies**: None.

## Review Focus

Please focus on the missing-node-pack row behavior:

- Single known pack with one affected node should stay compact and
locate the node from the pack label or locate icon.
- Known packs with multiple affected nodes should show a count, start
collapsed, and expand/collapse from either the chevron or title.
- Unknown packs should expose the affected node rows immediately,
including when there is only one affected node.
- Locate actions should remain attached to the affected node rows, not
to the parent pack when there are multiple nodes.
- The E2E fixture intentionally uses two missing nodes with the same
`cnr_id` and node sizes of `[400, 200]` to follow browser-test asset
guidance.

## Validation

- `pnpm format:check`
- `pnpm lint`
- `pnpm typecheck`
- `pnpm knip --cache` via pre-push hook
- `pnpm test:unit
src/components/rightSidePanel/errors/MissingPackGroupRow.test.ts
src/components/rightSidePanel/errors/MissingNodeCard.test.ts --run`
- `pnpm test:browser:local
browser_tests/tests/propertiesPanel/errorsTabMissingNodes.spec.ts
--project=chromium`
- Pre-commit hook: staged formatting, linting, `pnpm typecheck`, and
`pnpm typecheck:browser`

## Screenshots

This PR 
<img width="531" height="598" alt="스크린샷 2026-06-10 오전 1 54 31"
src="https://github.com/user-attachments/assets/9c0addeb-92d2-4cef-a4f3-35a87bbad308"
/>

old (Main)
<img width="509" height="807" alt="스크린샷 2026-06-10 오전 1 53 51"
src="https://github.com/user-attachments/assets/b8488f73-d8ed-4356-bd4c-fc678ea205f7"
/>
2026-06-10 09:59:50 +00:00
dante01yoon
b695686504 feat(billing): add DES-247 responsive variants to CreditsTile (FE-964)
Container-query the tile: narrow widths drop the used/remaining labels
and breakdown subtitle, stack the additional-credits value, and compact
the monthly summary numbers (105K left of 200K). Align the additional
credits tooltip copy with the updated design.
2026-06-10 18:08:02 +09:00
jaeone94
14f8fdebdd fix: refresh promoted combo host options after missing model reload (#12692)
## Summary

Fixes a Vue node subgraph case where the missing-model refresh flow
clears the missing-model error, but the promoted combo widget remains in
an invalid visual state because its hosted options snapshot is stale.

## Changes

- **What**: After `reloadNodeDefs()` refreshes combo option lists and
extension `refreshComboInNodes` hooks run, resync hosted options
snapshots for promoted combo widgets so Vue-rendered subgraph nodes see
the newly available model option.
- **What**: Adds a focused E2E regression for the missing-model refresh
path on a subgraph with a promoted `ckpt_name` widget.
- **What**: Hardens the E2E by cleaning up its `/object_info` route
override and asserting the widget's `aria-invalid` state rather than a
Tailwind implementation class.
- **Breaking**: None.
- **Dependencies**: None.

## Review Focus

This PR is intentionally a minimal patch for the missing-model refresh
path, not a broader subgraph architecture change.

Root cause: `reloadNodeDefs()` updates the live LiteGraph combo widget
options, including widgets inside subgraphs. However, Vue nodes render
promoted widgets from a hosted `WidgetState.options` snapshot. When a
missing model is downloaded and the missing-model refresh button reloads
node definitions, the source combo receives the updated model list, but
the promoted host snapshot can still contain the old option list. The
missing-model error and node-level state are cleared, while the Vue
combo still computes itself as invalid from stale options.

The fix keeps the existing host snapshot model intact. It simply resyncs
promoted combo host options after the normal combo refresh pipeline
finishes. This avoids changing promoted-widget ownership,
`useProcessedWidgets` merge precedence, or broader subgraph internals
while addressing the reported stale invalid state.

Why the helper is in `app.ts`: this sync is currently a single-call-site
post-step of `reloadNodeDefs()`, and the ordering is load-bearing. It
must run after the core combo refresh loop and after extension
`refreshComboInNodes` hooks so it captures both built-in and
extension-provided option changes. Keeping the small private helper next
to the refresh orchestration makes that sequence explicit and avoids
adding a new public subgraph helper or introducing a more visible
dependency cycle through `promotionUtils` for a narrow patch.

This is stacked on
`jaeone/fe-942-bug-error-indicators-persist-after-resolving-missing-model`,
so it is opened as a draft until the base PR lands.

## Test Plan

- `pnpm knip`
- `pnpm exec oxfmt --check src/scripts/app.ts
browser_tests/tests/propertiesPanel/errorsTabModeAware.spec.ts`
- `pnpm exec eslint --cache --no-warn-ignored src/scripts/app.ts
browser_tests/tests/propertiesPanel/errorsTabModeAware.spec.ts`
- `pnpm typecheck`
- `pnpm test:browser:local --grep "Refreshing a resolved promoted
missing model clears the combo invalid state"`
- First local run hit a `beforeEach` timeout while the dev server was
still warming custom-node/conflict-detection output.
  - Re-running against the warmed dev server passed.

## Screenshots (if applicable)

N/A. The behavioral E2E covers the visible invalid-state regression.
2026-06-10 04:36:45 +00:00
dante01yoon
e4ff4becb7 feat(billing): finalize CreditsTile layout to DES-247 (FE-964)
Match the finalized DES-247 credits tile: single monthly-usage bar (gold,
fills with the consumed portion) with a Monthly / Refills header and
"{used} used" + "{remaining} left of {total}" labels, a divider, and a single
Additional-credits row with a credit icon and "Used after monthly runs out"
subtitle. Drops the two-segment bar and colored dots; updates i18n + the
unit/e2e assertions accordingly.
2026-06-10 13:10:18 +09:00
dante01yoon
e12d3d7f40 test(billing): e2e for the Plan & Credits CreditsTile (FE-964)
Drives a raw page with mocked Firebase auth + stubbed boot endpoints so the
cloud app initializes without a backend, then asserts the DES-247 tile renders
its total/progress/monthly+additional breakdown/add-credits in Settings ▸
Workspace ▸ Plan & Credits. Bypasses the comfyPage fixture (which requires the
OSS devtools backend).
2026-06-10 13:10:18 +09:00
jaeone94
1b90696459 Simplify missing media error presentation (#12705)
## Summary

This is the second PR in the planned Workflow Overview error-tab
modernization stack. It follows the merged execution-error slice in
#12683 and focuses only on Missing Media / Missing Inputs.

The intent is to simplify missing-media errors from
media-type/file-resolution cards into compact node-level guidance.
Missing media is now presented as a flat list of affected node inputs,
matching the new catalog-driven item-row direction introduced by the
first PR while intentionally avoiding the larger missing-node,
swap-node, and missing-model redesigns.

This PR also removes the upload/library selection workflow from the
error tab. Missing media rows now help the user locate the affected
node/input directly, rather than attempting to resolve the missing file
inside the error card.

## Changes

- **What**: Simplifies the Missing Inputs card into a flat list of
affected node/widget rows.
- **What**: Displays each missing media row with a catalog-resolved item
label in the form `{nodeName} - {inputName}`.
- **What**: Makes both the row label and right-aligned locate icon
navigate to the affected node on canvas.
- **What**: Adds item-specific accessible labels for locate icon buttons
and an explicit focus-visible state for clickable row labels.
- **What**: Uses the existing `list-scale` transition pattern so missing
media rows animate consistently when they enter or leave.
- **What**: Removes the old image/video/audio sub-sections from the
missing-media card.
- **What**: Removes missing-media
upload/dropzone/library-selection/status/confirm/cancel UI, because
resolution is no longer handled inside this card.
- **What**: Removes stale E2E selectors and tests tied to the deleted
upload/library controls.
- **What**: Removes stale English missing-media locale keys for the
deleted controls; translated locale files are intentionally left to the
localization sync process.
- **What**: Removes stale missing-media store interaction state
(`expandState`, `uploadState`, `pendingSelection`) and the now-unused
name-based removal API that only supported the deleted upload/library
flow.
- **What**: Keeps the remaining widget/node/prefix candidate-removal
APIs used by the missing-media lifecycle.
- **What**: Carries `nodeType` through missing-media referencing nodes
so fallback labels remain correct when the same missing filename is
referenced by multiple node types.
- **Breaking**: None expected. This changes the missing-media error-tab
UX, but does not change the workflow schema or backend APIs.
- **Dependencies**: None.

## Planned stack

This PR is part of the same staged redesign plan described in #12683.
The goal is to keep each error category reviewable and stable before
moving to the next slice.

1. Execution errors, merged in #12683: validation, runtime, and prompt
error grouping/presentation.
2. Missing media, this PR: simplify image/video/audio missing-media
cards into node-level catalog item rows with locate actions.
3. Missing node and swap node: align missing-pack rows, nested node
references, install/replace actions, and locate behavior.
4. Missing model: unify OSS and Cloud presentation, simplify
download/import actions, and improve import/download progress behavior.

This PR intentionally does not change missing node packs, swap nodes,
missing models, runtime errors, validation errors, or prompt errors.

## Review Focus

- Missing media rows should now be node-level rows, not file-level rows
and not image/video/audio sections.
- If the same missing media filename is referenced by multiple nodes,
each affected node/input should render as its own row.
- Clicking either the row label or locate icon should focus the
corresponding affected node.
- Removed upload/library controls are intentional and should not be
restored in this PR.
- The missing-media store cleanup should not remove any state or API
still used by the remaining missing-media lifecycle.
- Only `rightSidePanel.missingMedia.missingMediaTitle` should remain in
the English locale for the old missing-media UI namespace; new row copy
lives in the error catalog.
- This PR should stay scoped to Missing Media / Missing Inputs and
should not include the larger missing model/node/swap redesign work.

## Screenshots (if applicable)

This PR
<img width="483" height="339" alt="스크린샷 2026-06-08 오후 8 26 43"
src="https://github.com/user-attachments/assets/e029411e-b1db-41cd-aa6e-2bd5d86762f6"
/>

Before (Main)
<img width="484" height="802" alt="스크린샷 2026-06-08 오후 8 32 19"
src="https://github.com/user-attachments/assets/c183c18d-babe-4f4e-b4e0-706ce41298de"
/>

## Validation

- `pnpm format`
- `pnpm test:unit src/platform/missingMedia/missingMediaStore.test.ts
src/components/rightSidePanel/errors/TabErrors.test.ts
src/platform/errorCatalog/errorMessageResolver.test.ts
src/platform/missingMedia/missingMediaScan.test.ts
src/platform/missingMedia/missingMediaAssetResolver.test.ts`
- `pnpm typecheck`
- `pnpm knip --cache`
- `pnpm test:browser:local
browser_tests/tests/propertiesPanel/errorsTabMissingMedia.spec.ts
--project=chromium`
- Pre-push hook: `pnpm knip --cache`

## Review notes

Claude review was run after the main implementation and again after
cleanup. No blocker or major issues remained. The only final minor
finding was stale inline i18n fixture data in `TabErrors.test.ts`, which
was removed in the final commit.
2026-06-09 06:52:35 +00:00