mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-16 16:58:33 +00:00
69fd1ddee78966eaae6ab783ae6b5951ecb9b2ad
5943 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
69fd1ddee7 |
fix: rename user-facing 'API Nodes' to 'Partner Nodes'
Update display strings in the sign-in dialog, cost breakdown, settings category, API key description, and login tooltip. i18n lookup keys and 'API Key' references are left unchanged. |
||
|
|
26cd975c1d |
refactor(load3d): extract Viewport3d base + SceneOverlay protocol (#12987)
## Summary Split Load3d into Viewport3d (model-agnostic viewport scaffolding) and Load3d (extends Viewport3d, adds loader/model/animation/HDRI/recording/gizmo). Viewport3d exposes only render-loop plumbing, layout, mouse status, camera orchestration, and the SceneOverlay protocol so future 3D node viewports can compose it without inheriting model machinery. - SceneOverlay protocol (attach/detach/update/onActiveCameraChange/dispose) gives any 3D node a managed lifecycle slot for plugging scene content into the viewport. - Viewport3d.setExternalActiveCamera(cam | null) for POV swap: renders from an externally-owned camera (e.g. a subject camera authored by an overlay), with OrbitControls detached and the view helper hidden. - ControlsManager.detach()/attach() back the POV control gating. - Two-phase init via Viewport3d.start() so subclass field assignments finish before any render-path code dispatches through overridden tickPerFrame / isActive. Behavior preserved for all 5 Load3d consumers (Load3D, Preview3D, PreviewGaussianSplat, PreviewPointCloud, SaveGLB). Sets up the upcoming CreateCameraInfo preview and other future Three.js-based 3D nodes (Pose Editor, Animation Director) to compose Viewport3d plus their own SceneOverlay implementation. |
||
|
|
c2968422e6 |
fix(billing): refresh workspace billing status after completed top-up (FE-932) (#12787)
## Summary A completed workspace top-up refreshed only the balance, leaving billing status — and `subscription.hasFunds` (derived from `statusData.has_funds`) — stale until the next status fetch. The completed handler now refreshes both. ## Changes - **What**: `TopUpCreditsDialogContentWorkspace.vue` completed branch — `await fetchBalance()` → `await Promise.all([fetchBalance(), fetchStatus()])` (both already exposed on `useBillingContext()`). - **Breaking**: none. ## Review Focus - Pre-existing bug (predates the B2 facade; `main`'s top-up already called `fetchBalance` only). Test validity proven by reverting to balance-only → the completed case goes red on the `fetchStatus` assertion. - Tests: completed → both refresh; pending / failed → neither (3 cases). typecheck / oxlint / eslint / stylelint / oxfmt / knip clean. Fixes FE-932 |
||
|
|
5acd76cb6d |
Add Desktop telemetry event sink (#12802)
## Summary - initialize a Desktop-only telemetry provider in ComfyUI_frontend - forward existing typed telemetry events through `window.__comfyDesktop2.Telemetry.capture` using the existing event names - move the Desktop 2 bridge typing to the shared ambient types and let run/execution telemetry fire when any provider is registered ## Paired change - Desktop PR: https://github.com/Comfy-Org/Comfy-Desktop/pull/1069 ## Validation - `pnpm typecheck` - `pnpm format:check` - `pnpm lint` - `pnpm knip` - `pnpm test:unit src/platform/missingModel/missingModelDownload.test.ts src/platform/telemetry/initDesktopTelemetry.test.ts src/platform/telemetry/providers/desktop/DesktopTelemetryProvider.test.ts` - YAML lint over tracked YAML files with `.yamllint` [MAR-240](https://linear.app/comfyorg/issue/MAR-240/frontend-telemetry-pipeline-for-desktop-app-eventsink-refactor) |
||
|
|
bc885f383c |
Decouple run telemetry context from providers (#12925)
## Summary Move run-button context assembly out of telemetry providers so telemetry can initialize without importing app-mode/workspace state. ## Changes - **What**: Providers now accept completed `RunButtonProperties`; run-button call sites use a workspace composable to build that payload. - **Dependencies**: None. |
||
|
|
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. |
||
|
|
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
|
||
|
|
05efee07ce |
Move Comfy Desktop bridge types into frontend (#12857)
## Summary Adds `@comfyorg/comfyui-desktop-bridge-types` as a workspace package in the frontend monorepo and changes the frontend app dependency to `workspace:*`. Adds a dedicated `Publish Desktop Bridge Types` workflow for publishing `packages/comfyui-desktop-bridge-types` by its own package version, without coupling it to the generated `@comfyorg/comfyui-frontend-types` release. The generated frontend types package still emits a concrete `@comfyorg/comfyui-desktop-bridge-types@0.1.2` dependency instead of leaking workspace/catalog protocol references. The Desktop2 missing-model path uses `window.__comfyDesktop2.isRemote()` when available, but falls back to the legacy `window.__comfyDesktop2Remote` marker so frontend rollout stays compatible with older Desktop builds. Paired Desktop PR: https://github.com/Comfy-Org/Comfy-Desktop/pull/1112 |
||
|
|
bc212e8a19 |
fix: remove unused export from ExportFormatOption interface (#12973)
## Summary Remove unused `export` keyword from `ExportFormatOption` interface. The interface is only used internally in `constants.ts` and is not imported elsewhere. Fixes knip "unused exported types" error. Co-authored-by: Connor Byrne <c.byrne@comfy.org> |
||
|
|
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> |
||
|
|
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.
|
||
|
|
ca2ead3c4a |
fix: guard workspace auth refresh races (#11726)
## Summary Fixes FE-485. This updates workspace auth refresh handling so stale in-flight refresh responses cannot overwrite a newer workspace context, and exhausted transient token exchange failures preserve the existing workspace context while its token is still valid. ## Changes - Add commit-time request-id guards before `switchWorkspace` writes workspace state, workspace token, `error`, or `sessionStorage`. - Track the current workspace token expiry in memory and use it to distinguish safe transient refresh failures from failures that must clear context. - Convert the stale refresh race coverage from expected-failing to a normal passing regression test. - Update transient retry coverage to assert valid context and `sessionStorage` preservation. ## Browser / E2E coverage No Playwright test was added because this bug is in the Pinia store race between mocked token-exchange promises, request IDs, token expiry, and `sessionStorage` commits. The deterministic unit spec directly controls the ordering that is not practical to force through the browser without real auth/session infrastructure and artificial network timing hooks. ## Validation - `pnpm format -- src/platform/workspace/stores/workspaceAuthStore.ts src/platform/workspace/stores/useWorkspaceAuth.test.ts` - `pnpm exec vitest run src/platform/workspace/stores/useWorkspaceAuth.test.ts` - `pnpm exec eslint src/platform/workspace/stores/workspaceAuthStore.ts src/platform/workspace/stores/useWorkspaceAuth.test.ts` - `pnpm exec oxlint src/platform/workspace/stores/workspaceAuthStore.ts src/platform/workspace/stores/useWorkspaceAuth.test.ts --type-aware` - `pnpm exec vue-tsc --noEmit --pretty false` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11726-fix-guard-workspace-auth-refresh-races-3506d73d365081b99df3c1bf3d0e008a) by [Unito](https://www.unito.io) --------- Co-authored-by: bymyself <cbyrne@comfy.org> |
||
|
|
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. |
||
|
|
cf7c68cd50 |
Update default workflow (#12804)
## Summary
Update the default workflow to use a more modern model than SD1.5. This
new workflow uses Z-Image Turbo and is the same workflow as the one in
the README for consistency.
## Changes
- **What**: `src/scripts/defaultGraph.ts`
## Screenshots (if applicable)
<img width="1920" height="1152"
alt="{2DD28B9F-A9E7-4DD7-8F07-AF7241F5702E}"
src="https://github.com/user-attachments/assets/6e6ee298-a786-4a8c-adf3-6452df08a995"
/>
---------
Co-authored-by: Connor Byrne <c.byrne@comfy.org>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
|
||
|
|
0a4021df99 |
1.47.2 (#12893)
Patch version increment to 1.47.2 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> |
||
|
|
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" /> |
||
|
|
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 |
||
|
|
edf3e1a682 |
refactor(litegraph): remove vestigial use_uuids node-id mode (#12930)
## Summary Removes LiteGraph's vestigial `use_uuids` node-id mode, which was never enabled anywhere in the codebase. ## Changes - **What**: Deletes the `LiteGraph.use_uuids` flag and the node-id branches that read it. Newly created/added/cloned nodes now always receive integer ids. The `asSerialisable` sort guard drops its `@ts-expect-error` in favor of an explicit `Number(a.id) - Number(b.id)` comparison. - **Breaking**: `LiteGraph.use_uuids` is removed from the public `LiteGraph` global. It defaulted to `false` and nothing ever set it to `true`, so behavior is unchanged for all real usage — but the property no longer exists on the global surface. Litegraph changes can affect downstream custom-node repos. `createUuidv4` / `LiteGraph.uuidv4` are intentionally kept — still used for subgraph ids, slot ids, and clipboard remapping. ## Review Focus - Confirm hard removal (vs. deprecate-then-remove no-op getter) is acceptable for the public `LiteGraph` surface. - `asSerialisable` sort now coerces ids via `Number()`; all serialised node ids are numeric, so ordering is unchanged. ## Notes - 2 litegraph test suites (`LGraphCanvas.clipboard.test.ts`, `SubgraphWidgetPromotion.test.ts`) fail at import with a pre-existing asset-URL error unrelated to this change (confirmed they fail with these changes stashed). All 909 tests that ran pass. Co-authored-by: Amp <amp@ampcode.com> |
||
|
|
305d209f6f |
fix: remove unused export from Load3dSerializedBase type (#12928)
## Summary Remove unused `export` keyword from `Load3dSerializedBase` type. The type is only used internally as the return type of `snapshotLoad3dState` and is not imported elsewhere. Fixes knip "unused exported types" error. Co-authored-by: Connor Byrne <c.byrne@comfy.org> |
||
|
|
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 |
||
|
|
6850d22d99 |
Redesign error overlay count and toast behavior (#12871)
## Summary Redesign the run error overlay so its count and copy use the same grouped error semantics as the redesigned Error tab, while keeping single-error toast copy precise. This is a stacked follow-up to #12828. The parent PR redesigns the Error tab cards and centralizes their grouped/row-based presentation. This PR applies the same mental model to the compact overlay shown from the Run button path, so users no longer see one count in the Error tab and a different count in the overlay. ## Changes - **What**: Align the error overlay count with the Error tab's grouped error count. - The overlay now reads the same grouped error surface that powers the Error tab hero instead of independently summing raw store error counts. - Validation/runtime/prompt execution groups use their grouped `count`. - Missing model/media/node/swap groups keep the row/group count semantics introduced by the Error tab redesign. - This avoids cases where the overlay headline says one number while the panel summarizes another. - **What**: Preserve the existing single-error toast behavior while adding explicit multi-row handling. - A single true leaf still uses the catalog/resolver toast title and toast message. - A single grouped execution error with multiple node/input items uses that group's title/message instead of the generic aggregate copy. - A single missing model/media group with multiple model/file rows uses the generic aggregate copy because it represents multiple actionable rows. - A single missing model/media row referenced by multiple nodes uses the group title/message, since there is still only one model/file to resolve. - Multiple top-level groups continue to use the aggregate "N errors found" style copy. - **What**: Restyle the overlay toast to match the new error-surface direction. - Adds a compact dark card with a destructive left accent and a visible outline for better contrast against the workspace. - Keeps the close button in the card header area. - Keeps the primary "View details" action, but adjusts spacing, size, and typography to better match the Figma direction. - Removes the older footer-style dismiss action so the overlay behaves like a focused status toast rather than a secondary dialog. - **What**: Share error grouping/count helpers instead of duplicating local logic. - Extracts execution item-list detection for reuse between the Error tab render path and count logic. - Extracts missing-model grouping/count helpers so missing-model row count semantics have one implementation. - Removes `groupedErrorMessages`, which became unused after the overlay copy decision moved to grouped error state. - **Breaking**: None. - **Dependencies**: None. ## Review Focus - **Stack boundary**: Please review this against `jaeone/fe-816-error-card-redesign`, not against `main` directly. The parent PR is #12828 and contains the Error tab card redesign that this overlay work builds on. - **Count semantics**: The visible overlay count intentionally changes from raw store counts to grouped Error tab counts. This is not just a refactor; it is the intended product behavior so the compact overlay and the panel hero agree. - **Overlay message branches**: The overlay can now choose between three message modes. The goal is to keep precise single-error copy where it is useful, but avoid showing one node-specific toast message when the overlay actually represents multiple actionable rows. | Branch | When it applies | Overlay title source | Overlay message source | Example output | | --- | --- | --- | --- | --- | | Aggregate summary | More than one top-level error group, or one missing model/media group with multiple actionable model/file rows. | Generic aggregate title using grouped count. | Generic aggregate message. | Title: `2 errors found`<br>Message: `Resolve them before running the workflow.`<br>Example case: one Missing Models group containing `first.safetensors` and `second.safetensors`. | | Group summary | Exactly one error group that is not a true single leaf, but should still be described by the group. This includes one execution catalog group with multiple items, or one missing model/media row referenced by multiple nodes. | The group's `displayTitle`. | The group's `displayMessage`. | Title: `Missing connection`<br>Message: `Required input slots have no connection feeding them.`<br>Example case: one validation group with `KSampler - model` and `KSampler - positive` rows. | | Single leaf toast | Exactly one group, one actionable row/card, and at most one node reference. | Resolver/catalog `toastTitle`. | Resolver/catalog `toastMessage`. | Title: `Model missing`<br>Message: `CheckpointLoaderSimple is missing missing.safetensors.`<br>Example case: one missing model file referenced by one node. | - **Scope control**: This PR intentionally does not redesign the full Error Overlay flow beyond the compact toast/card. It also does not revisit the deeper Error tab card layouts already handled in #12828. - **Accessibility**: The toast keeps `role="status"` for polite announcement semantics. The duplicate `aria-live` attribute was removed during cleanup because `role="status"` already implies polite live-region behavior. ## Validation - `pnpm format` - `pnpm lint` - `pnpm typecheck` - `pnpm knip` - `pnpm test:unit src/components/error/useErrorOverlayState.test.ts src/platform/missingModel/missingModelGrouping.test.ts src/components/error/ErrorOverlay.test.ts src/components/rightSidePanel/errors/useErrorGroups.test.ts` - Pre-commit staged checks passed. - Pre-push `knip` passed. ## Screenshots (if applicable) <img width="454" height="179" alt="스크린샷 2026-06-16 오후 6 00 10" src="https://github.com/user-attachments/assets/a85376ba-2b22-4cf8-a6fa-79f83fb8b244" /> <img width="453" height="179" alt="스크린샷 2026-06-16 오후 6 00 31" src="https://github.com/user-attachments/assets/d9a1d4bd-92ab-451a-bb79-e7cfbc3af7c6" /> <img width="486" height="148" alt="스크린샷 2026-06-16 오후 6 00 55" src="https://github.com/user-attachments/assets/b66faf96-65c8-4a22-9ff9-e8ccd450986e" /> <img width="395" height="127" alt="스크린샷 2026-06-16 오후 6 01 22" src="https://github.com/user-attachments/assets/c64443f9-0eba-4b2b-8049-c1887c788b1e" /> <img width="384" height="134" alt="스크린샷 2026-06-16 오후 6 01 30" src="https://github.com/user-attachments/assets/42f4fcae-b003-4df9-8f3a-0fda85a90880" /> <img width="376" height="129" alt="스크린샷 2026-06-16 오후 6 01 53" src="https://github.com/user-attachments/assets/ce9030d0-2a98-4b38-9e7d-7a9c3103960f" /> <img width="379" height="128" alt="스크린샷 2026-06-16 오후 6 02 01" src="https://github.com/user-attachments/assets/3d4ce356-1c22-4e3b-a1d0-fece351d9fbb" /> <img width="463" height="133" alt="스크린샷 2026-06-16 오후 6 02 33" src="https://github.com/user-attachments/assets/6ae13a44-02aa-4167-8878-4906db468ad6" /> |
||
|
|
543a39a6b0 |
fix(billing): DES review polish on workspace billing UI (#12917)
### before <img width="539" height="475" alt="Screenshot 2026-06-17 at 9 52 50 PM" src="https://github.com/user-attachments/assets/dd562cb4-870c-43de-aca4-81e0118735e9" /> ### after <img width="529" height="592" alt="Screenshot 2026-06-17 at 9 53 40 PM" src="https://github.com/user-attachments/assets/ba8ed01e-ac91-4654-bfaa-d8f73923f378" /> Design-review polish on the team-workspace billing UI (3 of the items from the Figma 'Team Plan - Workspaces' review). All three components are on main. ### 1. Remove dead 'Upgrade' badge in account popover `CurrentUserPopoverWorkspace.vue` — the white badge beside 'Plans & pricing' was gated on `canUpgrade`, which is hardcoded `false` (PRO is the only tier), so it never rendered. Removed the badge markup and the dead computed. (Figma node 2797-724189.) ### 2. Subscribe-to-Run button height `SubscribeToRun.vue` — used `size="sm"`, which didn't match the sibling run/queue button (an `h-8` button group it swaps with in the same slot via `CloudRunButtonWrapper`). Switched to `size="unset"` + `h-8 rounded-lg gap-1.5 px-4` to match. ### 3. Duplicate border/radius on member 'subscription inactive' dialog `useSubscriptionDialog.ts` — the layout dialog already zeroes the *content* border (`border-none shadow-none`), but the *root* pt kept only `bg-transparent`, so the dialog frame's default border+radius doubled with the card's own `rounded-2xl border`. Added `border-none rounded-none shadow-none` to root so only the card's single border/radius shows. (Figma node 3253-19473.) Surfaced during Billing V1 design review (team workspaces). --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
c5eb05a2e9 |
fix(billing): truncate long workspace names in the switcher (#12918)
The workspace name was hard-clipped by the switcher panel's `overflow-hidden` instead of truncating with an ellipsis. The name span had `min-w-0` but its parent row `<button>` (`flex flex-1`) did not, so the button kept its content width and the long name overflowed. Add `min-w-0` to the row button (and the name span) so the flex chain can shrink and the name truncates. Follow-up to FE-769 (#12763, merged) — the component shipped without this. ## Before / After <img width="1370" height="538" alt="fe769-before-after" src="https://github.com/user-attachments/assets/b05d6faf-9dfe-4c93-9941-3c0a9bbfcc2d" /> Verified live in the team-workspaces mock (long-named "Acme Studio Workspace"). ### after <img width="703" height="302" alt="Screenshot 2026-06-17 at 9 54 34 PM" src="https://github.com/user-attachments/assets/725c3175-b65f-4224-aca9-3de777c95e85" /> |
||
|
|
5a846db6cf |
feat(billing): role-aware run-lock for cancelled/inactive team plans (FE-978) (#12786)
## Summary Cancelled / inactive team plans keep members but lock runs; the run button and the subscription-required dialog are now role-aware — owners are routed to the pricing/subscribe flow, members (who cannot subscribe) see "contact your workspace owner to resubscribe". ## Changes - **What**: `SubscribeToRun.vue` becomes a role-aware locked run button (owner → "Subscribe to Run"; member → neutral locked "Run" + contact-owner tooltip; both open the subscription dialog). `SubscriptionRequiredDialogContentWorkspace.vue` branches on role (member → read-only contact-owner panel, no pricing/subscribe affordance; owner → existing pricing/preview; member view suppressed for `out_of_credits` so the active-but-low-credits path is unchanged). `subscription.inactive.*` i18n keys. - **Breaking**: none. ## Review Focus - Role source = `useWorkspaceUI().permissions.value.canManageSubscription` (owner / personal = true, member = false) — the same accessor `SubscriptionPanelContentWorkspace.vue` uses. - **No BE work**: the run-gate already exists server-side (`InactiveSubscriptionError`; `is_active` checked before funds). The lock is gated on `is_active`, the same field the orchestrator uses, so FE/BE stay consistent; leftover-credits-while-inactive remains blocked by design. - Complements #12785 (FE-878 precondition→modal routing); disjoint file sets. Design: DES-197, Figma 3253-18670 / 3253-18671 / 3246-13962. - Tests: `SubscribeToRun` (4) / `CloudRunButtonWrapper` (3) / `SubscriptionRequiredDialogContentWorkspace` role cases — member sees contact-owner (no subscribe), owner sees pricing, run locks on `!is_active` and unlocks when active (22 total); full `test:unit` green. Fixes FE-978 |
||
|
|
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> |
||
|
|
2a7340ec6c |
1.47.1 (#12862)
Patch version increment to 1.47.1 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org> |
||
|
|
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 |
||
|
|
7a877d0715 |
refactor(assets): extract getAssetStoredFilename helper (FE-733) (#12287)
## Summary L1 prerequisite cleanup, scoped to a single type-preserving refactor: extract `getAssetStoredFilename(asset)` to collapse the duplicated `isCloud && asset.asset_hash ? asset.asset_hash : asset.name` branch from `useMediaAssetActions.ts` into one helper in `assetMetadataUtils.ts`. No behavior change. Once BE-933/934 emit `file_path` and the cloud spec sync brings the field into generated types, only the helper internals change (collapse to `asset.file_path ?? asset.name`). ## Scope change (per review) The `mockFeatureFlags` test util and the exported `FeatureFlags` type that this PR originally also added have been **split out**. They had no live consumer in the open stack — FE-729~732 (#12322 / #12335 / #12375 / #12417) don't use them, and FE-780 / FE-781 (#12485 / #12486) still hand-roll inline `vi.hoisted` mocks — so shipping them here would add a public surface with no caller. They will be reintroduced bundled with the first PR that actually adopts the util, where `featureFlag`'s return type and the "all flags off vs. production defaults" semantics can be validated against a real consumer. ## Review fixes carried in this PR - Mock `@/platform/distribution/types` via an `importOriginal` spread so `isDesktop` / `isNightly` survive the wholesale replacement (only `isCloud` was re-hoisted before). - Trimmed the `getAssetStoredFilename` JSDoc; the BE-933/934 future-collapse is now a one-line `TODO` rather than a design-doc paragraph. ## Review Focus - The helper is intentionally named `getAssetStoredFilename` to disambiguate from the existing `getAssetFilename` (which targets `user_metadata.filename` / `metadata.filename` for serialized-identifier contexts — missing-model matching, filename schema validation) and `getAssetDisplayFilename` (UI labels). Folding the `isCloud && asset_hash` fallback into either of those would regress display/identifier sites where the cloud hash is never meant to surface. - Fixes FE-733 - Parent: FE-601 (L1 umbrella) - RFC: [Asset Identity Semantics](https://www.notion.so/comfy-org/RFC-Asset-Identity-Semantics-35a6d73d365080e59d59c98cebae779b) - Survey: [Asset FE Divergence Survey M1 Scope](https://www.notion.so/comfy-org/Assets-FE-Divergence-Survey-M1-Scope-3616d73d365080d0a9cbf5f2394c12f8) - Slack thread: https://comfy-organization.slack.com/archives/C0AUUTS2RQV/p1778815571949519 ## Screenshots (if applicable) N/A — no UI change. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12287-refactor-assets-extract-getAssetStoredFilename-helper-add-mockFeatureFlags-test-util-3616d73d365081c9a1c6e1982728a38a) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: Matt Miller <matt@miller-media.com> |
||
|
|
ac4105cca8 |
fix: Add missing save nodes in text replacement module (CORE-301) (#12837)
## Summary Fix issue where variables typed in the `filename_prefix` of the save nodes were not interpreted. See issue raised by users here: https://github.com/Comfy-Org/ComfyUI/pull/13850#issuecomment-4700771342 ## Changes - **What**: Added the following nodes to `src/extensions/core/saveImageExtraOutput.ts` - `SaveImageAdvanced` - `SaveSVGNode` - `SaveAudioMP3` - `SaveAudioOpus` - `SaveAudioAdvanced` |
||
|
|
36b57f1e83 |
feat: implement customer.io SDK & telemetry provider (#12878)
## Summary Adds a cloud-only Customer.io telemetry provider that forwards key frontend lifecycle events and registers the in-app messaging plugin, enabling low-latency intent-moment campaigns. ## Changes - **What**: - new `CustomerIoTelemetryProvider` (matching impl. and registration of other telemetry, dynamic import, tree shaken) - Triggered from 9 initial sources - Update telemetry scanner - **Dependencies**: `@customerio/cdp-analytics-browser` ## Review Focus - Matches other telemetry providers & is correctly removed from OSS builds ## Screenshots (if applicable) |
||
|
|
8c04f3261a |
Fix undated failed runs in job history grouping (#12879)
## Summary Fixes FE-874 by preventing terminal jobs that do not have an `execution_end_time` from being grouped under the `Undated` section in the expanded job history / queue UI. The production change is intentionally small: when grouping completed or failed jobs by date, the UI now falls back from `executionEndTimestamp` to `createTime`. ```ts ts = task.executionEndTimestamp ?? task.createTime ``` This keeps the existing preference for execution completion time when it is available, while still giving pre-execution terminal jobs a meaningful date bucket based on when the job was created. ## Root Cause Some terminal jobs, especially failures that happen before execution actually starts, can legitimately arrive without `execution_end_time`. This matches the backend semantics: if there is no execution start, there may be no execution end timestamp either. Before this change, the frontend grouping logic treated missing `executionEndTimestamp` as if there were no usable date at all for terminal jobs. That caused failed jobs with a valid `create_time` to appear in the `Undated` group. At the same time, the list sorting logic already used `createTime`, so those jobs could appear near the top of the list while still being labeled as `Undated`. That mismatch made recent failed jobs look like they had no date, even though the job creation timestamp was present. ## What Changed - Updated `useJobList` date grouping for terminal jobs: - `completed` and `failed` jobs still use `executionEndTimestamp` when available. - If `executionEndTimestamp` is missing, they now fall back to `createTime`. - Added regression coverage for terminal jobs without an execution end timestamp: - A failed job without `executionEndTimestamp` is grouped by `createTime`. - A completed job without `executionEndTimestamp` is also covered because the production fallback applies to both terminal states in the same code path. - Cleaned up the `useJobList` test harness by replacing the mocked `vue-i18n` module with a real `createI18n` instance per mount. - This follows the repo testing guidance to avoid mocking `vue-i18n`. - Each composable mount now receives a fresh i18n instance, avoiding shared mutable i18n state between tests. ## User Impact Failed jobs that never reached execution will no longer show up under `Undated` when they still have a valid creation timestamp. They will instead appear under the correct date group, such as `Today`, `Yesterday`, or a localized month/day label. This should make the expanded job history easier to scan and avoid the confusing case where recent failed runs appear at the top while also being labeled as undated. ## E2E Regression Coverage Rationale I did not add a Playwright regression test under `browser_tests/` for this fix because the regression is isolated to the timestamp selection used by `useJobList` when it builds date groups. There is no changed user interaction, navigation flow, API request shape, route handling, or browser-only behavior. The existing browser coverage already verifies that the Job History sidebar opens, renders active and terminal jobs, and filters completed/failed jobs using the mocked jobs route fixture. Adding an E2E for this specific case would require creating another mocked `/api/jobs` response with a terminal job that has `create_time` but no `execution_end_time`, opening the sidebar, and asserting the rendered date header. That would mostly duplicate the composable-level assertion through the DOM while adding extra moving parts around relative date labels, locale/timezone formatting, and the virtualized job list. The regression is therefore covered more directly and deterministically at the unit level in `src/composables/queue/useJobList.test.ts`. The new test drives the same grouping pipeline that the UI consumes and asserts that terminal jobs without `executionEndTimestamp` are grouped by `createTime` instead of falling into `Undated`. I also verified the test fails against the pre-fix implementation with `['Undated']` and passes with the fallback. ## Notes This PR does not attempt to synthesize an execution end time. The backend can validly omit `execution_end_time` for jobs that never started execution. The frontend fix is limited to display grouping: if there is no execution end timestamp, use the already-present creation timestamp as the grouping date. If product requirements later need the exact terminal failure timestamp for pre-execution failures, that would require a separate backend/API timestamp such as a terminal-state or update timestamp. This PR only fixes the current display fallback. ## Validation Local validation run before publishing: ```bash pnpm test:unit src/composables/queue/useJobList.test.ts pnpm exec eslint src/composables/queue/useJobList.ts src/composables/queue/useJobList.test.ts pnpm exec oxlint src/composables/queue/useJobList.ts src/composables/queue/useJobList.test.ts --type-aware git diff --check -- src/composables/queue/useJobList.ts src/composables/queue/useJobList.test.ts ``` The commit hook also ran successfully during the final amend and passed: ```bash pnpm exec oxfmt --write ... pnpm exec oxlint --type-aware --fix ... pnpm exec eslint --cache --fix ... pnpm typecheck ``` ## Screenshots Before <img width="346" height="624" alt="Screenshot 2026-06-17 1:35:06 AM" src="https://github.com/user-attachments/assets/02269f57-038a-4f06-9892-0758ad84d2c7" /> After <img width="352" height="632" alt="Screenshot 2026-06-17 1:35:37 AM" src="https://github.com/user-attachments/assets/251cd762-2c88-4af6-8218-4af1915727b6" /> |
||
|
|
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 |
||
|
|
0df2b05790 |
fix: encode large copy payload metadata in chunks (#12847)
## Summary Fix Ctrl+C copy for large subgraphs by encoding clipboard metadata in bounded byte chunks instead of spreading the full serialized payload into a single `String.fromCharCode(...)` call. ## Root Cause <img width="648" height="33" alt="스크린샷 2026-06-15 오후 4 46 52" src="https://github.com/user-attachments/assets/09aec159-fd10-4979-bfb2-51aec9b51a63" /> Ctrl+C uses the native `copy` event path in `useCopy.ts` so ComfyUI can write serialized node metadata into the system clipboard as `text/html`. That metadata supports the cross-app / cross-window copy-paste path. For Unicode safety, the current code first converts the serialized node JSON to UTF-8 bytes with `TextEncoder`, then converts those bytes into a binary string for `btoa`. The bug was in this conversion step: ```ts String.fromCharCode(...Array.from(new TextEncoder().encode(serializedData))) ``` When a selected subgraph is large enough, the UTF-8 byte array becomes too large to spread as function arguments. The browser throws `RangeError: Maximum call stack size exceeded` before clipboard metadata is written, so Ctrl+C appears to fail for large subgraphs. The right-click / menu copy path was not affected in the same way because it uses LiteGraph's internal `copyToClipboard()` path directly and does not go through this system clipboard metadata encoding step. ## Changes - **What**: Convert UTF-8 bytes to a binary string in `0x8000` byte chunks before passing the result to `btoa`. - **Why**: This preserves the existing UTF-8 safe cross-app clipboard metadata format while avoiding the JavaScript argument-count limit that caused the stack overflow. - **Fallback**: Wrap system clipboard metadata encoding/writing in `try/catch` so the internal `canvas.copyToClipboard()` result is still produced even if the metadata bridge fails unexpectedly. - **Dependencies**: None ## Review Focus - Chunking is only used while building the binary string for base64 encoding. The clipboard payload format remains unchanged. - Multi-byte UTF-8 data remains safe because chunking happens at the byte-string construction layer; paste still reassembles the full byte stream before `TextDecoder` decodes it. - The unit test exercises the actual `useCopy` copy handler with a large serialized payload, Unicode metadata, and a partial final chunk. ## Test Plan - `vitest run src/composables/useCopy.test.ts` - pre-commit hook: `oxfmt`, `oxlint`, `eslint`, `typecheck` - pre-push hook: `pnpm knip` No E2E was added because this regression is isolated to deterministic clipboard metadata encoding in `useCopy`. The unit test exercises the actual `copy` event handler with a large serialized payload and Unicode metadata, avoiding a large workflow fixture and slower browser coverage for behavior that does not require canvas rendering or end-to-end UI orchestration. Linear: [FE-858](https://linear.app/comfyorg/issue/FE-858/bug-ctrlc-copy-keyboard-shortcut-does-not-work-on-large-subgraphs) |
||
|
|
c36da042d0 |
Redesign error tab cards with summary hero and unified sections (#12828)
## Summary Redesigns the Errors tab cards to match the new Figma error-panel spec (file `Czv0JcCfcUiizeEZURevpq`): every error group is now wrapped in a single bordered card led by an error-count summary hero, with each category rendered as a collapsible section whose count lives in a circular badge rather than a parenthetical title suffix. > Rebased onto `main` after #12793 was merged. This PR now contains only the error-card redesign slice. ## Changes - **What**: - **New `ErrorCardSection.vue`** — the shared section shell used by every error type. Renders a 32px header (circular count badge + neutral title + `actions` slot + collapse chevron) and a `TransitionCollapse` body. Replaces the per-group `PropertiesAccordionItem`, dropping the old octagon-alert icon + red title + `(n)` suffix and the sticky-header behavior. - **`TabErrors.vue`** — wraps all groups in one `rounded-lg` card bordered with `secondary-background`. Adds a summary **hero** (large severity-colored total count, vertical divider, "N Errors detected / Resolve before running the workflow"). Moves the per-group action buttons (Install All / Replace All / missing-model Refresh) into the section's `actions` slot. Adds `getGroupCount()` / `totalErrorCount` and switches content background to `interface-panel-surface`. Most of the line count here is re-indentation from the template restructure, not behavior change. - **`missingErrorResolver.ts`** — drops the `formatCountTitle` helper so display titles are `"Missing Models"` instead of `"Missing Models (4)"`; the badge now carries the count. Toast titles/messages are untouched. - **`ErrorNodeCard.vue`** — restyles the runtime/validation error-log box to the Figma spec: borderless `base-foreground/5` surface, `ERROR LOG` header, 12px non-mono body at 50% opacity, inset footer divider with Get Help / Find on GitHub links. - **Row components** (`MissingModelRow`, `MissingPackGroupRow`, `SwapNodeGroupRow`, `MissingMediaCard`, `MissingNodeCard`, `MissingModelCard`) — align spacing, fonts, badges, and button sizes with Figma: 12px row labels, `size="sm"` (24px) action buttons, 16px count badges (`rounded-sm`, `secondary-background-hover`, 9px), 32px reference-row heights, `px-3` card padding. Model-name wrapping is kept independent of its count badge and link button so they never reflow into the metadata sub-label. - **i18n** — adds `errorsDetected` (pluralized), `resolveBeforeRun`, `expand`, `collapse` to `en/main.json`. - **Breaking**: None. No store, composable, action, or data-flow changes — all handlers and emitted events are preserved. The only user-visible copy change is the removal of the `(n)` count suffix from section titles. ## Review Focus - **Title copy change**: `"Missing Models (4)"` → `"Missing Models"`. Search-filter matching against the old `(n)` string no longer applies, but the count is shown by the badge and the hero total. - **Sticky header removed**: section headers no longer pin to the top on scroll (intentional per the new design). - **Collapse click target**: the old single-button header (which nested action buttons inside a `<button>` — invalid HTML) is split into a separate title button and chevron button. Behavior is unchanged and accessibility improves; the empty space beside an action button no longer toggles collapse. - All semantic colors map to existing design-system tokens (no `dark:` variants, no hardcoded hex). Verified the artifact hex values match the tokens (e.g. `#262729` = `secondary-background`, `#e04e48` = `destructive-background-hover`, `#171718` = `interface-panel-surface`). ## Follow-up This PR intentionally keeps the error-count ownership cleanup out of the current diff so the card redesign remains reviewable. A follow-up PR will centralize error counting around a single source of truth so the Errors tab summary hero, section badges, and any overlay surfaces cannot drift from one another. That follow-up will also address the current count mismatch in the ErrorOverlay and continue the ErrorOverlay redesign there, instead of expanding this PR after review. ## Screenshots (if applicable) After <img width="603" height="703" alt="스크린샷 2026-06-13 오후 1 00 02" src="https://github.com/user-attachments/assets/065d7c19-9748-4e99-9b43-675a31e92949" /> <img width="601" height="197" alt="스크린샷 2026-06-13 오후 1 01 07" src="https://github.com/user-attachments/assets/0fa1fbda-9091-4a45-9eca-e99c43089c0e" /> <img width="617" height="612" alt="스크린샷 2026-06-13 오후 1 02 43" src="https://github.com/user-attachments/assets/3d67a057-bf65-4e51-bcf5-70ecce851826" /> <img width="495" height="723" alt="스크린샷 2026-06-13 오후 1 03 28" src="https://github.com/user-attachments/assets/6dcc4021-0fc3-4955-a68b-c0533c66a3cf" /> --------- Co-authored-by: GitHub Action <action@github.com> |
||
|
|
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> |
||
|
|
06dda1fb38 |
feat: Load3DAdvanced uploads to input/3d (#12851)
## Summary As discussed with team, we should keep upload folder as /input/3d folder in new Load 3D node |
||
|
|
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. |
||
|
|
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" /> | |
||
|
|
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"
/>
|
||
|
|
e832380c33 |
refactor(billing): unify cancel-status polling into billingOperationStore (B8 / FE-970) (#12788)
## Motivation - **Why B8 exists**: two divergent BillingOp pollers poll the same op-status endpoint with different policies (hand-rolled 2× / 5s cap / 30 attempts vs the store's 1.5× / 8s / 120s). Once B1 (FE-966) routes personal flows through the facade, a single cancel op would be polled by **both** — duplicate requests, with two timeout policies racing on the same state. - **The latent bug — silent failure on a money path**: the bespoke poller treated timeout as a silent return, so `cancelSubscription` resolved and the dialog showed "cancelled successfully" while the backend op could still be pending or fail later. The root cause is structural: the poll outcome was fire-and-forget — no caller consumed it, so there was no channel through which a failure could surface. - **The fix — outcome as an awaited contract**: `startOperation` returns the terminal outcome as a `Promise<BillingOperation>`; `cancelSubscription` awaits it and throws on any non-`succeeded` terminal. Every outcome must now flow through the caller, making silence structurally impossible (timeout/failure → error toast). - **The trade-off this creates**: "the terminal promise always settles" becomes load-bearing — the dialog's loading state hangs on it, and a never-settling path would be worse than the old silence (a permanently locked dialog). The terminal-promise hardening below, and its regression tests, enforce that guarantee. ## Summary Two divergent BillingOp pollers (hand-rolled `useWorkspaceBilling.pollCancelStatus` vs `billingOperationStore`) are unified into one — cancel-status polling now runs through `billingOperationStore`; `pollCancelStatus` and its bespoke backoff/timers/state are removed. ## Changes - **What**: `billingOperationStore` gains `'cancel'` in `OperationType`; `startOperation` now returns `Promise<BillingOperation>` resolving on the terminal outcome (existing subscription / topup callers unaffected — fire-and-forget preserved). `useWorkspaceBilling.cancelSubscription` awaits the shared poller and throws on any non-`succeeded` terminal. One backoff config = store's 1.5× / 8s / 120s. - **Terminal-promise hardening**: the terminal promise always settles — a rejected post-success status/balance refresh no longer leaves `cancelSubscription` hanging with the dialog locked open (`Promise.allSettled`), and a duplicate `startOperation` for an in-flight op joins the same terminal promise instead of resolving instantly with a `pending` snapshot (which the cancel path would read as failure). - **Breaking**: none — the `cancelSubscription(): Promise<void>` contract is unchanged for `CancelSubscriptionDialogContent` / `useBillingContext`. ## Review Focus - **Intentional behavior change**: a cancel **timeout** is now a terminal outcome that **throws** (`billingOperation.cancelTimeout`), so the dialog surfaces an error toast — instead of the old silent success-ish return (which could show success while the op was still pending). Success / failure semantics otherwise preserved (success → status refresh + `isSubscribed: false`; failure → throw with message). - FE-only refactor (B8); cleanest after FE-904 but independent of it. Relates FE-932 (shared status-refresh path). ## Tests 90 unit tests green across the four affected suites (fake timers for all polling): - **`billingOperationStore.test.ts` (33)** — cancel terminal outcomes (succeeded / failed / timeout) resolve the awaited promise with the right status + i18n message; cancel suppresses the store's toasts and settings-dialog side effects; success refreshes status/balance and sets `isSubscribed: false`; backoff progression, 8s cap, 2-min timeout; transient poll errors keep polling. Regression guards for the hardening: post-success refresh failure still settles the terminal promise (reproduced as a hang before the fix), and a duplicate `startOperation` joins the in-flight terminal promise instead of resolving with a `pending` snapshot. - **`useWorkspaceBilling.test.ts`** — `cancelSubscription` drives the shared poller; throws the op error on `failed`, throws on `timeout`, falls back to a generic message when `errorMessage` is absent; a failing cancel API propagates without starting the poller. - **`CancelSubscriptionDialogContent.test.ts` (8)** — locks the dialog half of the behavior change: a rejected `cancelSubscription` shows an error toast and keeps the dialog open; success closes the dialog with a success toast. - **`useSubscriptionCheckout.test.ts`** — unchanged, confirms fire-and-forget callers are unaffected. Both hardening regressions were proven red→green locally: before the fix the terminal-hang test timed out at 5s and the duplicate-start test resolved `pending`. Gates: vue-tsc / oxlint type-aware / eslint / oxfmt clean; full CI green including all Playwright shards and the cloud project. The existing `cancelSubscriptionDialog.spec.ts` e2e (@ui, open/close/escape flows) is unchanged and green; cloud-backend e2e for billing flows is tracked separately in FE-991. Fixes FE-970 |
||
|
|
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 |
||
|
|
99a2320a42 |
1.47.0 (#12850)
Minor version increment to 1.47.0 **Base branch:** `main` --------- Co-authored-by: dante01yoon <6510430+dante01yoon@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> |
||
|
|
2b0bcda41f |
chore(deps): update oxc toolchain to 1.69 (fixes Windows oxlint OOM) (#12838)
## Summary Update the oxc toolchain to 1.69 to fix the Windows fixed-size allocator OOM that made oxlint commit ~130 GB per process and intermittently panic with "Insufficient memory to create fixed-size allocator pool". ## Changes - **What**: Bump catalog deps — `oxlint` 1.59.0 → 1.69.0, `oxlint-tsgolint` 0.20.0 → 0.23.0, `oxfmt` 0.44.0 → 0.54.0, `eslint-plugin-oxlint` 1.59.0 → 1.69.0. oxlint 1.69 includes the Windows `VirtualAlloc` reserve/commit fix (oxc-project/oxc#22124, shipped in oxc 0.131.0). - **What**: Add `--no-error-on-unmatched-pattern` to the lint-staged oxlint command. oxlint 1.69 now exits non-zero when no lintable files match (e.g. a yaml/lock-only commit), which broke the pre-commit hook; this mirrors the existing stylelint `--allow-empty-input`. - **Dependencies**: oxc toolchain only; no runtime/app dependencies changed. ## Review Focus - `pnpm format:check` reports no reformatting (oxfmt 0.54 produces identical output on all 3655 files). - `pnpm lint` passes (0 errors). Note: oxlint 1.69 adds `typescript/no-useless-default-assignment`, which surfaces ~840 non-blocking warnings repo-wide (not auto-fixable). Left enabled here; happy to disable in `.oxlintrc.json` in a follow-up if preferred. - Verified locally that the new oxlint LSP commits ~0 GB instead of ~130 GB. --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: GitHub Action <action@github.com> |
||
|
|
b9112f9bd7 |
feat(load3d) FE-999: export point cloud / splat files as-is (#12810)
## Summary PLY, SPZ, SPLAT and KSPLAT have no THREE.js exporter, so the 3D node now offers them as export options only when the loaded file already uses that format, and exports the original file unchanged instead of attempting a conversion. Mesh files keep the convertible GLB/OBJ/STL/FBX set. Splat models (spz/splat/ksplat and gaussian-splat ply) previously had exportable: false, which hid the export UI entirely; enable it so these formats can be downloaded as-is. Also fixes STL export dropping its originalURL fast-path arg. ## Screenshots (if applicable) https://github.com/user-attachments/assets/8417c697-eb25-4cfb-af44-3855b814fa5d |
||
|
|
b5b124fa9e |
refactor: extract Cloud-JWT mint + dormant unified refresh lifecycle (FE-950) - step 2 (#12704)
## Summary Behind `unified_cloud_auth` (default OFF), extract the `/auth/token` Cloud-JWT mint out of `switchWorkspace` and add a parallel mint/refresh lifecycle that writes a dedicated, **dormant** `unifiedToken` slot — read by no consumer until PR3 — so this PR alone cannot change which token any request carries. Stacked on #12702 (PR1, flag registration). Base will auto-retarget to `main` once #12702 merges. ## Changes - **What**: - Extract `requestToken(workspaceId?)` (network + parse only) from `switchWorkspace`. An id-less `{}` body mints the personal-workspace token; a concrete `workspace_id` keeps the legacy body byte-identical. The legacy `switchWorkspace`/`refreshToken` path is behaviorally unchanged (still owns its own state writes, `isLoading`, request-id, and `scheduleTokenRefresh`). - `mintAtLogin()` mints the personal default into `unifiedToken`, gated on `unifiedCloudAuthEnabled` **only** (decoupled from `teamWorkspacesEnabled`). Silent — no `isLoading` flash. - `refreshUnified()` — parallel buffer-based refresh off the parsed `expires_at` (reuses `TOKEN_REFRESH_BUFFER_MS`, no hardcoded TTL). The legacy `refreshToken` is left untouched so its team-workspaces gate is preserved. - `remintUnifiedOnce()` — guarded single re-mint primitive for PR3's 401 path; a shared `unifiedRefreshRequestId` stale-guard prevents concurrent mints clobbering each other (last mint to start wins). - `clearWorkspaceContext()` tears down the unified slot + timer on logout. - `authStore`: mint at login for cloud users; add `notifyTokenRefreshed()` and gate the Firebase `onIdTokenChanged` rotation bump off under the flag (the unified lifecycle becomes the sole rotation driver — no double rotation). - **Breaking**: None. Every flag-OFF path is byte-for-byte the current cascade. ## Review Focus - **Dormancy**: `unifiedToken` is written only by the flag-gated mint lifecycle and read by no consumer in this PR (the `getAuthHeader`/`getAuthToken` flip is PR3). Tests assert `workspaceToken` is never touched by `mintAtLogin`. - **Legacy parity**: the `requestToken` extraction is a pure refactor — the full existing `switchWorkspace`/`refreshToken` suite is the regression net and stays green; flag-OFF + team-workspaces-OFF fires zero network from any timer. - **Concurrency**: `unifiedRefreshRequestId` stale-guard covers a 401-driven re-mint racing the scheduled refresh. - **Rotation**: `notifyTokenRefreshed` fires only on a refresh re-mint (never the initial login mint or a switch), and the legacy L129 bump is gated off under the flag. Tests cover legacy parity, the dormant slot, buffer-based refresh (re-mint fires off parsed expiry), rotation-trigger semantics, the single re-mint primitive (no loop on persistent 401), the concurrency stale-guard, logout teardown, and full flag-OFF dormancy. Part of FE-950 (single Cloud-JWT provider at login, Phase 1). Follow-up: PR3 flips consumers + adds the 401-retry interceptor. |
||
|
|
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> |
||
|
|
f212c7d409 |
1.46.14 (#12807)
Patch version increment to 1.46.14 **Base branch:** `main` --------- Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> |
||
|
|
1d5801d6ef |
feat: track funnel telemetry attributes (#12778)
## Summary Adds the frontend telemetry attribution needed to analyze settings, app-mode, and sharing funnel usage for MAR-321: re-enables three funnel events that were disabled by default, attaches app-mode/view-mode/dock-state context to UI click, run, and share events, and adds a per-session `shell_layout` snapshot plus right-side-panel toggle tracking. ## Changes - **What**: - Removes `setting_changed`, `template_filter_changed`, and `ui_button_click` from the code-default `DEFAULT_DISABLED_EVENTS` lists in the Mixpanel and PostHog providers, so these events now send by default (see deployment note). - `ui_button_click` now requires an `element_group`; all call sites are tagged (`sidebar`, `queue`, `actionbar`, `breadcrumb`, `error_dialog`, `errors_panel`, `graph_menu`, `graph_node`, `selection_toolbox`, `node_library`, `workflow_actions`, `cloud_notification`, `app_mode`, `top_menu`, `right_side_panel`) and the GTM provider forwards the field. - Run events (`run_button_clicked`, GTM `run_workflow`) now carry required `view_mode`/`is_app_mode` plus a new `dock_state` (`docked`/`floating`), read from the `Comfy.MenuPosition.Docked` localStorage key by a new `getActionbarDockState()` util. - Share funnel events (`share_flow`, `share_link_opened`, `shared_workflow_run`) now carry required `view_mode`/`is_app_mode`. A new `useShareFlowContext()` composable dedupes the source/view-mode context across the share dialog, URL copy field, and `useShareDialog`. GTM `share_flow` forwards the new fields and still omits `share_id`. - `shared_workflow_run` attribution is snapshotted onto the queued job at queue time, so switching app/graph mode while a job runs no longer misattributes the completion event (falls back to live values when no snapshot exists). - New `shell_layout` event fired once per session at graph-ready (cloud only): `view_mode`, `is_app_mode`, `dock_state`, `actionbar_position`, `active_sidebar_tab`, `right_side_panel_open`, `bottom_panel_open`, `open_workflow_tabs`. Forwarded by Mixpanel and PostHog; not sent to GTM. - The right side panel open button (top menu) and close button now fire `ui_button_click` (`right_side_panel_opened`/`right_side_panel_closed`), covering the panel open-rate gap. - **Dependencies**: None. ## Review Focus - `view_mode`/`is_app_mode` changed from optional to required (typed as `AppMode`) on run/share metadata — check no call sites were missed. - The queue-time snapshot in `executionStore` (`queuedJob.viewMode ?? mode.value`) and its regression test. - Share IDs remain limited to the providers/events that already carry share attribution (GTM still strips `share_id`). - `shell_layout` cadence is once per session (graph-ready idle callback), matching the gap analysis's "session snapshot" wording. Linear: MAR-321 Validation: - `pnpm test:unit src/platform/telemetry/providers/cloud/PostHogTelemetryProvider.test.ts src/platform/telemetry/providers/cloud/MixpanelTelemetryProvider.test.ts src/platform/telemetry/providers/cloud/GtmTelemetryProvider.test.ts src/platform/telemetry/utils/getShellLayoutSnapshot.test.ts src/platform/workflow/sharing/components/ShareWorkflowDialogContent.test.ts src/platform/workflow/sharing/composables/useSharedWorkflowUrlLoader.test.ts src/stores/executionStore.test.ts src/components/TopMenuSection.test.ts src/components/graph/selectionToolbox/InfoButton.test.ts src/components/rightSidePanel/errors/useErrorActions.test.ts src/views/GraphView.test.ts` - `pnpm typecheck` - `pnpm lint` - `pnpm knip` - `git diff --check` ## Deployment note `telemetry_disabled_events` is currently unset in the prod/staging/test dynamicconfig rows, so the code-default change here is what enables these events. The remote value remains available as a kill switch, but it **replaces** the code defaults rather than merging: if ops sets it to re-disable an event, the list must include every event that should stay disabled (`tab_count_tracking`, `node_search`, `node_search_result_selected`, `help_center_*`, `workflow_created`), not just the new ones. |
||
|
|
193f23e8c2 |
Revert "feat: default search to essentials when graph is empty" (#12814)
Reverts Comfy-Org/ComfyUI_frontend#12377 |
||
|
|
afd42525fe |
B2 - refactor(billing): complete the billing facade — resubscribe/topup + status fields (FE-904) (#12622)
## What Implements **B2 — Complete the billing facade** from the FE billing-divergence survey. Adds the members missing from the shared `BillingContext` so components stop bypassing `useBillingContext` with raw `workspaceApi` calls. Part of the billing convergence plan — **FE-904** (parent **FE-903**). ## Why this PR — an *enabling* refactor (near-zero standalone user value) On its own B2 changes no endpoint and is user-invisible (see **Behavioral impact**). Its entire purpose is to be the **prerequisite** that unblocks the rest of the convergence — it gives the facade a single entry point and the missing capability/state surface that the next levers depend on: - **Unblocks B3 — repoint direct-bypass consumers (the next PR; a live bug fix).** `SubscribeButton` (`current_tier`) and `PostHogTelemetryProvider` (the `subscription_tier` person property) currently read the **legacy** `useSubscription` tier, so the value is **stale/empty for team users today** (telemetry + analytics are wrong right now). They can only be repointed to a correct, workspace-aware tier by sourcing it from the facade — which requires the **`tier`** (and `renewalDate`, for `FreeTierDialog`) fields **this PR adds**. Without B2 there is literally no facade `tier` to read. - **Unblocks B6 — orientation banners.** The 6 billing-state banners need `billingStatus` / `subscriptionStatus`, exposed here. - **Unblocks B1 — dispatcher flip (personal → workspace path).** B1 can only collapse the personal/team fork once (a) every billing operation flows through the facade — no raw `workspaceApi` bypass left — and (b) the facade actually supports `resubscribe`/`topup`. This PR removes the last bypasses and completes the action surface so the unified personal path will work. (B1 itself stays gated on the BE-DATA unification.) ## Changes - **Contract** (`composables/billing/types.ts`): `BillingActions` gains `resubscribe()` and `topup(amountCents)`; `BillingState` gains `billingStatus`, `subscriptionStatus`, `tier`, `renewalDate`. Exported `BillingStatus`, `BillingSubscriptionStatus`, `CreateTopupResponse` from `workspaceApi`. - **Workspace adapter** (`useWorkspaceBilling`): real wiring — `workspaceApi.resubscribe()` / `createTopup()`, surfaces `statusData.billing_status` / `subscription_status` / `subscription_tier` / `renewal_date`. - **Legacy adapter** (`useLegacyBilling`): equivalents — `resubscribe` = fresh checkout via `useSubscription`; `topup` converts **cents → dollars** through `purchaseCredits`; `billingStatus` = `null` (no legacy concept); `subscriptionStatus` synthesized from active/cancelled flags. - **Dispatcher** (`useBillingContext`): proxies the new members. - **Orphaned callers migrated** off raw `workspaceApi`: - `SubscriptionPanelContentWorkspace.vue` → `resubscribe()` - `useSubscriptionCheckout.ts` → `resubscribe()` - `TopUpCreditsDialogContentWorkspace.vue` → `topup(amountCents)` ## Notes - **Unit divergence absorbed:** the facade standardizes `topup` on **cents**; the legacy adapter converts to dollars for `/customers/credit`. - **FE-only, no backend dependency** — safe to merge/deploy standalone; independent of the B1 dispatcher flip (which is gated on the BE-DATA unification). ## Behavioral impact (verified — safe to merge/deploy standalone) This is a structural refactor: **endpoints, request payloads, and fetch counts are unchanged**, and there is **no user-visible change** on OSS/Desktop or Cloud-personal. - **OSS / Desktop** (`teamWorkspacesEnabled` off): no change. The only B2 code that runs is the eager `useAuthActions()` in `useLegacyBilling` setup — side-effect-free, and already instantiated transitively via `useSubscription` today. New computeds are lazy with zero readers; new legacy `resubscribe`/`topup` are never invoked (their callers are team-only surfaces). - **Cloud personal**: no change. The migrated handlers are structurally unreachable on the legacy path (dialog/panel variant gating, `isCancelled` gated to `!isInPersonalWorkspace`). - **Cloud team**: same endpoints/payloads/refresh counts. **One intentional behavioral nuance:** routing `resubscribe`/`topup` through the facade now toggles the shared `useBillingContext().isLoading` flag during the call (the previous raw `workspaceApi` calls did not). This is deliberate — it aligns these two with every other facade action (`subscribe`, `cancelSubscription`, …). Net effect is at most a brief loading-indicator flicker in the subscription panel; no change to network, ordering, or state correctness. > Follow-up (pre-existing, out of scope): **FE-932** — a completed top-up refreshes balance but not status, so `subscription.hasFunds` can be briefly stale. Predates B2 (`main` did balance-only too); to be fixed with B6. > Import-cycle note: this closes `useBillingContext → useLegacyBilling → useAuthActions → useBillingContext`. It is module-eval safe — every cross-cycle call is at composable-runtime, none at module top level. ## Verification - `vue-tsc --noEmit`: clean. - `oxlint --type-aware` on touched files: 0 errors / 0 warnings. - Runtime no-op confirmed by an adversarial code-path review across the 3 build targets (OSS / Cloud-personal / Cloud-team). - eslint + unit tests: deferred to CI. Survey: **FE Billing API Divergence — Personal vs Team Workspace** (Notion) — notes D4, P6, T1, E7, E9. > Draft: opened for early review of the facade shape and the legacy-equivalent semantics (esp. legacy `resubscribe` = fresh checkout, and the cents/dollars conversion). --------- Co-authored-by: Alexander Brown <drjkl@comfy.org> |