Commit Graph

94 Commits

Author SHA1 Message Date
Alexander Brown
b4ae6344d7 Brand local node IDs (#13085)
## Summary

Adds a branded local `NodeId` helper and starts separating local node
identity from serialized workflow IDs.

## Changes

- **What**: Adds central `NodeId` parsing/branding helpers, migrates
nearby widget identity types, keeps queue results at the serialized
boundary, and removes misleading workflow `NodeId` usage from execution
error maps.

## Review Focus

Check that the first migration slice keeps serialized/API IDs as raw
`number | string` while local UI/store IDs use the branded string type.

## Caveat

`SUBGRAPH_INPUT_ID` and `SUBGRAPH_OUTPUT_ID` are now branded local
`NodeId` string values internally instead of numeric sentinels.
Reviewers should double-check extension compatibility for callers that
import `Constants` and compare those values numerically.

## Screenshots (if applicable)

N/A

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: AustinMroz <austin@comfy.org>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 22:54:04 +00:00
Dante
4b979f4ad0 feat(dialog): migrate mask editor + 3D viewer dialogs to the Reka renderer (FE-578) (6a -1) (#12848)
## Summary

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

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

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

## Why this is safe to land alone

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

## Changes

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

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

## Quality gates

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

## Out of scope (stacked in #12593)

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


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

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

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

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

### 3D viewer — maximized (maximize-wins dimension re-assertion in
`DialogContent`)
| Before (PrimeVue) | After (Reka) |
|---|---|
| <img width="430" alt="3D viewer maximized before"
src="https://github.com/user-attachments/assets/b705a4d5-4657-41ad-b6f3-95e54494ac9b"
/> | <img width="430" alt="3D viewer maximized after"
src="https://github.com/user-attachments/assets/188de427-ab58-45a9-8666-967b2908c320"
/> |
2026-06-15 13:13:03 +00:00
Terry Jia
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
2026-06-14 04:48:06 -04:00
Terry Jia
25205c0f55 feat: add Load3DAdvanced node (#12723)
## Summary
add Load3DAdvanced node, without FE render, upload BE and HDRI.
BE https://github.com/Comfy-Org/ComfyUI/pull/14316

## Screenshots (if applicable)

https://github.com/user-attachments/assets/e561c919-bb52-4904-97da-fb01885762a7
2026-06-10 13:51:50 -04:00
Terry Jia
be131f7e9a feat(load3d): register Preview3DAdvanced extension (#12527)
## Summary
Preview3DAdvanced is a Preview3D variant that emits live viewport state
as outputs (model_file, camera_info, model_3d_info, width, height) plus
a width/height pair. Reuses the LOAD_3D capture widget for the viewport
but
skips upload/clear buttons (it has no model_file widget). 

Adds a camera-only + gizmo serializeValue path (no scene/mask/normal
image uploads since no image outputs are emitted) and an onExecuted that
loads the saved preview model via
Load3DConfiguration.configureForSaveMesh and applies the input
camera_info forwarded via PreviewUI3D when one was connected, so a chain
of Preview3DAdvanced nodes propagates camera state in a single run
instead of lagging one hop per execution.

Drops the per-node camera lock toggle (retainViewOnReload) across all 3D
nodes (Load3D, Preview3D, Preview3DAdvanced).
The viewport now always preserves the user's adjusted framing after the
first model load; the toggle was rarely needed and made chained
propagation ambiguous. Saved workflows with retainViewOnReload in their
cameraConfig continue
to load, the field is simply ignored at runtime.

camera_info / model_3d_info resolved by execute: the input when
connected, otherwise the viewport's own live state. Applying them
overrides the preserved viewport in the input case and is a no-op echo
when the viewport already matches. Only the first model_3d_info entry is
applied, single-object viewer currently.

BE: https://github.com/Comfy-Org/ComfyUI/pull/14175

## Screenshots (if applicable)


https://github.com/user-attachments/assets/42536469-e188-492a-9864-cdb6bfa76e97
2026-06-04 18:09:12 -04:00
Terry Jia
7df62ca75e feat: add PreviewGaussianSplat + PreviewPointCloud extensions (#12545)
## Summary
Two dedicated 3D viewer extensions for the splat / point-cloud.

- Comfy.PreviewGaussianSplat targets backend node 'PreviewGaussianSplat'
(.ply / .spz / .splat / .ksplat).
- Comfy.PreviewPointCloud targets backend node 'PreviewPointCloud' (.ply
point clouds).

PLY auto-dispatch, no more user-facing engine choice for 3DGS:

**Please be aware that I have not yet implemented any UI optimizations
on the frontend for world models such as World Labs' Marble, no WSAD
controls, no scale optimization yet**

BE: https://github.com/Comfy-Org/ComfyUI/pull/14194

## Screenshots (if applicable)
ksplat file:
<img width="2714" height="1391" alt="image"
src="https://github.com/user-attachments/assets/9024db9d-20e9-44ea-ab14-500810d2946a"
/>
splat file:
<img width="2938" height="1410" alt="image"
src="https://github.com/user-attachments/assets/de768fa5-9d55-4560-9fb3-b218b96ea0c7"
/>
spz file:
<img width="1729" height="845" alt="image"
src="https://github.com/user-attachments/assets/cc09e568-77c9-45b3-a6cc-8f5d1062f3ec"
/>
ply (splat) file:
<img width="1702" height="843" alt="image"
src="https://github.com/user-attachments/assets/2a51c2ce-046b-4843-9e58-634bc45cbcce"
/>
ply (point cloud) file:
<img width="1701" height="842" alt="image"
src="https://github.com/user-attachments/assets/db75808e-3481-4ecc-8582-e4fec21163fd"
/>
2026-06-01 14:25:06 -04:00
Terry Jia
d02c5d374f chore: tidy up retain-view-on-reload follow-ups (#12441)
## Summary
Drop a no-op cn() wrapper on the static camera-switch icon and trim the
two over-long comments around setRetainViewOnReload / _loadModelInternal
down to one line each.
2026-05-25 17:20:08 +00:00
Jukka Seppänen
3da6e1766e feat: optional retain camera view on Load3D model reload (#12440)
When comparing outputs from 3D generations, it's very hard to see small
differences since the camera always resets. This adds an option to lock
the camera, so only the model refreshes.

## Summary

Adds an opt-in per-node toggle that preserves the current camera view
(position, target, zoom, camera type) across model loads in Load3D /
Load3DAnimation nodes, instead of resetting to default framing.

## Changes

- **What**: New `retainViewOnReload?: boolean` field on `CameraConfig`,
a `Load3d.setRetainViewOnReload()` setter wired through the existing
`useLoad3d` camera-config watcher, capture/restore logic in
`Load3d._loadModelInternal`, and a lock-icon toggle button in
`CameraControls.vue` below the FOV slider. Preference persists via the
existing `node.properties['Camera Config']` mechanism.

## Review Focus

- **First-load semantics**: retain only kicks in once a model has
successfully loaded at least once (`hasLoadedModel` flag), so the
default `setupForModel` framing wins on a fresh node. `clearModel()`
resets the flag so the next load also reframes.
- **Restore order vs. `SceneModelManager.setupModel`**: the scene model
manager unconditionally calls `setupForModel` during a load, which
clobbers the camera. The restore in `_loadModelInternal` runs *after*
the load completes, on top of that framing.
- **Camera-type mismatch**: if the saved state's `cameraType` differs
from the currently active camera, `toggleCamera()` runs before
`setCameraState()` so the perspective/orthographic camera being restored
is actually the active one. Covered by a dedicated test.
- **Scope**: only wired through `useLoad3d` (LiteGraph node controls).
The full-page viewer (`useLoad3dViewer` / `ViewerCameraControls`) is
deliberately not extended — the modal is mostly a one-shot
view-and-close flow, so retain there would add surface area for an
uncommon use case.
- **Failed loads**: `hasLoadedModel` only flips inside `if
(modelManager.currentModel)`, so a load that produces no model leaves
the flag where it was. Captured camera state is still applied on top,
which effectively no-ops since nothing reset it.


## Video


https://github.com/user-attachments/assets/880d6ad1-28a9-4413-83a3-8323d05d904a
2026-05-23 08:47:30 -04:00
Terry Jia
0588ca45b3 test: remove stale primevue mocks from load3d tests (#12350)
## Summary
The load3d components no longer import primevue/select or
primevue/checkbox, so the vi.mock blocks targeting them had no effect.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12350-test-remove-stale-primevue-mocks-from-load3d-tests-3666d73d365081f692dbe91e16d594c3)
by [Unito](https://www.unito.io)
2026-05-22 19:32:18 +00:00
Terry Jia
52d77e6ee0 chore: upgrade sparkjs to 2.x and three to 0.184 (#12396)
## Summary
- Spark 2.x requires SparkRenderer in scene tree; add it in SceneManager
and protect it in clearModel so model reloads don't dispose the splat
renderer.
- three 0.184 OrbitControls listens on ownerDocument; drop redundant
pointermove/up .stop in Load3D containers so the document listener can
receive events.
- Narrow Texture.image type for 0.184 strict typing.
2026-05-21 10:06:20 -04:00
Terry Jia
cd2f4677c2 FE-719 feat(load3d): add FBX export support (#12323)
## Summary
implement fbx export, using our own lib fbx-exporter-three

## Screenshots (if applicable)


https://github.com/user-attachments/assets/80012338-d065-4a00-a9a0-0a2e73d67db4

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12323-FE-719-feat-load3d-add-FBX-export-support-3656d73d365081ef901ffe880ae9568a)
by [Unito](https://www.unito.io)
2026-05-20 06:52:56 -04:00
Alexander Brown
25c2d828c0 test: enable vitest/consistent-each-for and migrate .each → .for (#12161)
*PR Created by the Glary-Bot Agent*

---

Enables the oxlint rule `vitest/consistent-each-for` (configured to
prefer `.for` for `test`, `it`, `describe`, and `suite`) and migrates
every `.each` parameterized test in the repo to `.for`. Using `.for`
avoids accidentally splatting tuple elements into separate callback
arguments and exposes `TestContext` as the second callback argument.

The first commit covers the 38 lint-detected files (88 callsites):
renames `.each` → `.for` and updates callback signatures to destructure
when the data is an array of tuples (objects/primitives already work
unchanged with `.for`).

The follow-up commit addresses code review feedback: oxlint's rule does
not recognize `test.each` on extended test bases
(`baseTest.extend(...)`) and skips files in `ignorePatterns`
(`src/extensions/core/*`). These were converted manually so the policy
is uniform across the codebase.

## Verification

- `node_modules/.bin/oxlint src` — 0 errors, 0 `consistent-each-for`
violations
- `pnpm typecheck` — passes
- `pnpm test:unit` — all modified test files pass; pre-existing
environmental flakes (`GraphView.test.ts`, `ColorWidget.test.ts`, etc.,
unchanged here and flaky on `main` in this sandbox) are unrelated
- `pnpm lint` / `pnpm knip` — clean
- Manual verification: 362 tests across 6 representative converted
suites re-run in an interactive shell — all passing

Manual UI verification (Playwright/screenshots) is not applicable:
changes are test-file-only refactors with no production runtime or UI
behavior change.

## Notes on `.for` semantics

- Array-of-tuples (`[[a, b], ...]`) passes the tuple as a single arg, so
callbacks were changed from `(a, b) => …` to `([a, b]) => …`.
- Array-of-objects (`[{a}, …]`) already used destructuring — unchanged.
- Array-of-primitives (`['a', …]`) — callback signature unchanged.
- A handful of complex cases use a small `type Case = [...]` alias plus
`it.for<Case>([...])` to preserve tuple inference where TS narrowed
unions otherwise broke parameter types.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12161-test-enable-vitest-consistent-each-for-and-migrate-each-for-35e6d73d3650810c9417e07bdd9f27a2)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
2026-05-11 23:11:51 -07:00
Kelly Yang
5ebf5e03ae refactor(load3d): replace PrimeVue Select/Slider/Checkbox with Reka UI (#12020)
Replace PrimeVue components in 3D node viewer controls with the
project's Reka UI equivalents across 7 files.

## Changes

| File | Replaced |
|------|---------|
| `AnimationControls.vue` | `Select` × 2 (speed + animation) |
| `ViewerModelControls.vue` | `Select` × 2 (up direction + material
mode) |
| `ViewerCameraControls.vue` | `Select` + `Slider` (camera type + FOV) |
| `ViewerExportControls.vue` | `Select` (export format) |
| `PopupSlider.vue` | `Slider` |
| `ViewerLightControls.vue` | `Slider` |
| `ViewerSceneControls.vue` | `Checkbox` → native `<input
type="checkbox">` |

## Implementation notes

- `Select` uses `@/components/ui/select/*` compound components. Numeric
model values (animation speed index) are stringified at the binding
boundary and converted back on update, matching Reka `SelectRoot`'s
`string`-only `modelValue` contract.
- `Slider` uses `@/components/ui/slider/Slider.vue`. Single-number
`defineModel` values are wrapped in a `computed` array and unwrapped in
the update handler, following the pattern established in
`LightControls.vue`.
- No new Reka UI wrapper components were created — existing ui/select
and ui/slider primitives were used directly.

## Test 

https://github.com/user-attachments/assets/afca0fc8-a7b6-49ee-b221-ee5725bd127e
1. AnimationControls.vue
- **Add Load3D node** → Upload an animated GLB file (e.g., a character
model).
- **Node preview top bar:** Play/Pause button, speed dropdown, animation
name dropdown, and progress bar.

2. PopupSlider.vue
- **Hover over Load3D preview:** Icon buttons appear in the left
toolbar.
- **"Light Intensity" button (bulb icon)** → Slider pops up on the
right.
- **"FOV" button (view icon)** → Slider pops up on the right.

3. ViewerCameraControls.vue
- **Load3D node** → Settings panel (top-right) → **"Camera"** tab.
- **Features:** Camera type dropdown (Perspective / Orthographic), FOV
slider (visible in Perspective mode).

4. ViewerExportControls.vue
- **Settings panel** → **"Export"** tab.
- **Features:** Format dropdown (GLB / OBJ / STL), Export button.

5. ViewerLightControls.vue
- **Settings panel** → **"Light"** tab.
- **Features:** Light intensity slider.

6. ViewerModelControls.vue
- **Settings panel** → **"Model"** tab.
- **Features:** "Up direction" dropdown, Material mode dropdown
(Wireframe / Normal, etc.).

7. ViewerSceneControls.vue
- **Settings panel** → **"Scene"** tab.
- **Features:** Background color picker, "Show grid" checkbox, upload
background image button.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> UI component swap touches multiple interactive viewer controls
(selects/sliders/checkbox), so small binding/typing differences (string
vs number, array slider values) could cause subtle regressions despite
test updates.
> 
> **Overview**
> Replaces PrimeVue `Select`, `Slider`, and `Checkbox` usages across
Load3D viewer controls with the project’s Reka UI-based primitives
(`@/components/ui/select/*`, `@/components/ui/slider/Slider.vue`) and a
native checkbox.
> 
> Updates v-model wiring to match the new components’ contracts: selects
now bind via string `modelValue` with explicit number casting where
needed, and sliders now wrap single numeric values into `[number]`
arrays with corresponding update handlers. Unit tests are updated to
mock the new UI components and their updated event/value shapes.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
46f99db256. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12020-refactor-load3d-replace-PrimeVue-Select-Slider-Checkbox-with-Reka-UI-3586d73d365081f58601d93031016afd)
by [Unito](https://www.unito.io)
2026-05-06 19:30:25 -04:00
Terry Jia
bb74ec94de test(load3d): add unit tests for 9 previously-untested controls (#11730)
## Summary
Mirror the maskeditor coverage approach for load3d sub-components. Each
component gets behavior tests covering rendering, conditional branches,
v-model bidirectional sync, and emitted events.

- ViewerLightControls: setting-store min/max/step + v-model
- ViewerExportControls: format dropdown + click-to-export
- ViewerCameraControls: type select, FOV slider visibility
- ViewerSceneControls: with/without bg image branches, render mode
- PopupSlider: trigger toggle, click-outside dismissal, defaults
- CameraControls: switch button, FOV PopupSlider visibility
- ExportControls: trigger popup, format selection, click-outside
- AnimationControls: empty-list bypass, controls, time formatting
- ViewerControls: dialog open routing + onClose wiring

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11730-test-load3d-add-unit-tests-for-9-previously-untested-controls-3506d73d365081eaa9e7c5d0b922fc14)
by [Unito](https://www.unito.io)
2026-04-28 19:23:09 -04:00
Terry Jia
fc2a4e82cf feat(load3d): bind UI capability gating to ModelAdapterCapabilities (#11711)
> Final piece of the PLY / 3D Gaussian Splatting series. Previous PR
made `ModelAdapterCapabilities` load-bearing on the engine side; the UI
was still gating off `isSplatModel` / `isPlyModel` proxies. This PR
routes the viewer and the viewer-mode dialog through the capability
fields directly, so the same source of truth that drives `Load3d`
behavior also drives what the user sees. Eighth and last in the series
splitting up.

## Summary

Snapshot `Load3d.getCurrentModelCapabilities()` into 5 Vue refs on each
model load and pipe them through the existing `Load3D` /
`Load3DControls` / `Load3dViewerContent` / `ModelControls` /
`ViewerModelControls` / `Preview3d` props. Replaces the format-specific
`:is-splat-model` / `:is-ply-model` props and the hardcoded "splat →
drop light/gizmo/export" subtraction with additive capability gates. No
engine behavior changes — capability values are what previous PR already
produces; UI now consumes them.

## Changes

- **`useLoad3d.ts` / `useLoad3dViewer.ts`**: 5 new refs
(`canFitToViewer` / `canUseGizmo` / `canUseLighting` / `canExport` /
`materialModes`) refreshed on every load via
`load3d.getCurrentModelCapabilities()`. `useLoad3dViewer` extracts the
snapshot into a single `captureAdapterFlags(source)` helper because it
runs in three places (initializeViewer / initializeStandaloneViewer /
loadStandaloneModel).
- **`Load3D.vue`**: gate the fit-to-viewer button on `canFitToViewer`;
pass capability refs to `Load3DControls` instead of `isSplatModel` /
`isPlyModel`.
- **`Load3DControls.vue`**: build `availableCategories` additively
(`['scene','model','camera']` plus `light` / `gizmo` / `export` if their
capability is true) rather than subtracting from a fixed list when
`isSplatModel` is true. Forwards `materialModes` to `ModelControls`.
- **`Load3dViewerContent.vue`**: gate the light / gizmo / export sidebar
sections on the capability refs; pass `materialModes` to
`ViewerModelControls`.
- **`ModelControls.vue` / `ViewerModelControls.vue`**: drop the local
`materialModes` computed (which derived its options from `isPlyModel`
and a hardcoded mesh list) and accept `materialModes` as a `readonly
MaterialMode[]` prop. An empty array hides the dropdown entirely.
- **`Preview3d.vue`** (renderer linearMode): mirror the prop swap on the
standalone preview path.

## Review Focus

- **Capability prop wiring is the only public-API change for child
components**. `ModelControls` and `ViewerModelControls` lost
`hideMaterialMode` / `isPlyModel` props. Any extension that imported
these components directly will need to migrate, but they're internal
`src/components/load3d/controls/**` files and not part of the documented
extension surface.
- **Empty-`materialModes` semantics**: previously hidden via
`:hide-material-mode`; now hidden via `materialModes.length === 0`.
`SplatModelAdapter` declares `materialModes: []`, so the splat case
keeps the same behavior — the dropdown disappears. PLY adds
`'pointCloud'` to the array, so the dropdown picks up that mode
automatically without the controls needing an `isPlyModel` branch.
- **`captureAdapterFlags` runs after every load completes**, so
switching between mesh and splat in the same viewer instance updates the
chrome correctly. Verified via the new `Load3D.test.ts` /
`Load3dViewerContent.test.ts` cases.
- **Capability gating is inclusive of `canFitToViewer`** in this PR even
though `Load3DControls` has no fit category — the fit-to-viewer floating
button on `Load3D.vue` is what reads it. PLY's `fitToViewer: true` means
the button stays visible for PLY users.

## Coverage

| File | Stmts | Branch | Funcs |
|---|---|---|---|
| `Load3D.vue` (modified) | 53.3% | **95.5%** | 83.3% |
| `Load3DControls.vue` (modified) | 77.5% | **94.8%** | 86.4% |
| `Load3dViewerContent.vue` (modified) | 60.6% | 72.1% | 54.5% |
| `controls/ModelControls.vue` (modified) | 16.3% | 0% | 0% |
| `controls/viewer/ViewerModelControls.vue` (modified) | **100%** |
**100%** | **100%** |
| `composables/useLoad3d.ts` (modified) | 78.7% | 64.5% | 71.4% |
| `composables/useLoad3dViewer.ts` (modified) | 76.0% | 52.1% | 66.7% |

Four new test files (`Load3D.test.ts` / `Load3DControls.test.ts` /
`Load3dViewerContent.test.ts` /
`controls/viewer/ViewerModelControls.test.ts`) cover the new capability
gating directly: each component is rendered with capability flags
toggled on/off and the appropriate sidebar / dropdown / button
visibility is asserted. Capability prop forwarding from `Load3D.vue` →
`Load3DControls.vue` and from `Load3dViewerContent.vue` →
`ViewerModelControls.vue` is exercised end-to-end.

`controls/ModelControls.vue` is the legacy node-side ModelControls — its
existing tests live elsewhere and were not in this PR's scope; the diff
line covered (the `v-if="materialModes.length > 0"` swap) is exercised
by the new `Load3DControls.test.ts` cases that drive a non-empty / empty
`materialModes` through. `Preview3d.vue` (renderer linearMode) has no
test file in the project; the prop swap there is the same shape as the
`Load3D.vue` swap which is covered.

`useLoad3d.ts` / `useLoad3dViewer.ts` percentages are roughly the
pre-existing baseline. The diff lines (the 5 new refs and the
`captureAdapterFlags` helper) are exercised by the existing composable
tests via the mock that now stubs `getCurrentModelCapabilities()`.

73 new component unit tests; 393 total load3d-related tests pass on this
branch.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11711-feat-load3d-bind-UI-capability-gating-to-ModelAdapterCapabilities-3506d73d365081b3af68f30e3f728e24)
by [Unito](https://www.unito.io)
2026-04-28 16:39:06 -04:00
Christian Byrne
ef59f46495 refactor: migrate cn imports from @/utils/tailwindUtil shim to @comfyorg/tailwind-utils directly (#11453)
*PR Created by the Glary-Bot Agent*

---

## Summary

- Replace all `cn` / `ClassValue` imports from the
`@/utils/tailwindUtil` re-export shim with direct imports from
`@comfyorg/tailwind-utils` across 198 source files in `src/` and 3 in
`apps/desktop-ui/`
- Delete both shim files (`src/utils/tailwindUtil.ts` and
`apps/desktop-ui/src/utils/tailwindUtil.ts`)
- Add explicit `@comfyorg/tailwind-utils` dependency to
`apps/desktop-ui/package.json`
- Update documentation references in `AGENTS.md`,
`docs/guidance/design-standards.md`, and
`docs/guidance/vue-components.md`

Fixes #11288

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11453-refactor-migrate-cn-imports-from-utils-tailwindUtil-shim-to-comfyorg-tailwind-utils--3486d73d365081ec92cce91fbf88e6e4)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-04-22 18:39:57 -07:00
pythongosssss
9522f68ae6 test: additional load3d e2e coverage (#11521)
## Summary

Expands the e2e coverage for the load3d node

## Changes

- **What**: 
- test recording, grid and background upload

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11521-test-additional-load3d-e2e-coverage-3496d73d3650814595e1eabe97448993)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-04-22 20:03:37 -04:00
Terry Jia
deba72e7a0 gizmo controls (#11274)
## Summary
Add Gizmo transform controls to load3d

- Remove automatic model normalization (scale + center) on load; models
now appear at their original transform. The previous auto-normalization
conflicted with gizmo controls — applying scale/position on load made it
impossible to track and reset the user's intentional transform edits vs.
the system's normalization
- Add a manual Fit to Viewer button that performs the same normalization
on demand, giving users explicit control
- Add Gizmo Controls (translate/rotate) for interactive model
manipulation with full state persistence across node properties, viewer
dialog, and model reloads
- Gizmo transform state is excluded from scene capture and recording to
keep outputs clean

## Motivation
The gizmo system is a prerequisite for these potential features:
- Custom cameras — user-placed cameras in the scene need transform
gizmos for precise positioning and orientation
- Custom lights — scene lighting setup requires the ability to
interactively position and aim light sources
- Multi-object scene composition — positioning multiple models relative
to each other requires per-object transform controls
- Pose editor — skeletal pose editing depends on the same transform
infrastructure to manipulate individual bones/joints

Auto-normalization was removed because it silently mutated model
transforms on load, making it impossible to distinguish between the
original model pose and user edits. This broke gizmo reset (which needs
to know the "clean" state) and would corrupt round-trip transform
persistence.

## Screenshots (if applicable)

https://github.com/user-attachments/assets/621ea559-d7c8-4c5a-a727-98e6a4130b66

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11274-gizmo-controls-3436d73d365081c38357c2d58e49c558)
by [Unito](https://www.unito.io)
2026-04-18 22:45:06 -04:00
Kelly Yang
20255da61f feat(load3d): add optional HDRI environment lighting to 3D preview nodes (#10818)
## Summary

Adds `HDRIManager` to load `.hdr/.exr` files as equirectangular
environment maps via **three.js** `RGBELoader/EXRLoader`
- Uploads HDRI files to the server via `/upload/image` API so they
persist across page reloads
- Restores HDRI state (enabled, **intensity**, **background**) from node
properties on reload
- Auto-enables "**Show as Background**" on successful upload for
immediate visual feedback
- Hides standard directional lights when HDRI is active; restores them
when disabled
- Hides the Light Intensity control while HDRI is active (lights have no
effect when HDRI overrides scene lighting)
- Limits HDRI availability to PBR-capable formats (.gltf, .glb, .fbx,
.obj); automatically disables when switching to an incompatible model
- Adds intensity slider and "**Show as Background**" toggle to the HDRI
panel

## How to Use HDRI Environment Lighting
1. Load a 3D model using a Load3D or Load3DViewer node (supported
formats: .gltf, .glb, .fbx, .obj)
2. Open the control panel → go to the Light tab
3. Click the globe icon to open the **HDRI panel**
4. Click Upload HDRI and select a` .hdr` or `.exr` file
5. The environment lighting applies automatically — the scene background
also updates to preview the panorama
6. Use the intensity slider to adjust the strength of the environment
lighting
7. Toggle Show as Background to show or hide the HDRI panorama behind
the model

## Screenshots



https://github.com/user-attachments/assets/1ec56ef0-853e-452f-ae2b-2474c9d0d781



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10818-feat-load3d-add-optional-HDRI-environment-lighting-to-3D-preview-nodes-3366d73d365081ea8c7ad9226b8b1e2f)
by [Unito](https://www.unito.io)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Adds new HDRI loading/rendering path and persists new
`LightConfig.hdri` state, touching Three.js rendering, file uploads, and
node property restoration. Risk is moderate due to new async flows and
potential compatibility/performance issues with model switching and
renderer settings.
> 
> **Overview**
> Adds optional **HDRI environment lighting** to Load3D previews,
including a new `HDRIManager` that loads `.hdr`/`.exr` files into
Three.js environment/background and exposes controls for enable/disable,
background display, and intensity.
> 
> Extends `LightConfig` with an `hdri` block that is persisted on nodes
and restored on reload; `useLoad3d` now uploads HDRI files, loads them
into `Load3d`, maps scene light intensity to HDRI intensity, and
auto-disables HDRI when the current model format doesn’t support it.
> 
> Updates the UI to include embedded HDRI controls under the Light panel
(with dismissable overlays and icon updates), adjusts light intensity
behavior when HDRI is active, and adds tests/strings/utilities
(`getFilenameExtension`, `mapSceneLightIntensityToHdri`, new constants)
to support the feature.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b12c9722dc54a227004bdc383e3bf583504ebdce. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Terry Jia <terryjia88@gmail.com>
2026-04-12 05:55:48 -04:00
Terry Jia
277ee5c32e test: add E2E tests for Load3D model upload and drag-drop and basic e2e for 3d viewer (#10957)
## Summary
Add tests verifying real model loading:
- Upload cube.obj via file chooser button
- Drag-and-drop cube.obj onto the 3D canvas
- Add data-testid to LoadingOverlay for stable test selectors.
Add tests verifying 3d viewer openning:
- Open viewer from Load3D node via expand button, verify canvas and
controls sidebar
- Cancel button closes the viewer dialog

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10957-test-add-E2E-tests-for-Load3D-model-upload-and-drag-drop-and-basic-e2e-for-3d-viewer-33c6d73d3650810c8ff8ed656a5164a6)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2026-04-09 21:36:07 -04:00
Kelly Yang
4f3a5ae184 fix(load3d): fix squashed controls in 3D inspector side panel (#10768)
## Summary

Fixes squashed `input controls` (color picker, sliders, dropdowns) in
the 3D asset inspector side panel.

## Screenshots 

before
<img width="3012" height="1580" alt="image"
src="https://github.com/user-attachments/assets/edc6fadc-cdc5-4a4e-92e7-57faabfeb1a4"
/>

after
<img width="4172" height="2062" alt="image"
src="https://github.com/user-attachments/assets/766324ce-e8f7-43fc-899e-ae275f880e59"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10768-fix-load3d-fix-squashed-controls-in-3D-inspector-side-panel-3346d73d365081e8b438de8115180685)
by [Unito](https://www.unito.io)
2026-04-01 00:34:37 -04:00
Christian Byrne
60e8da308f fix: resolve nodes in subgraphs for image copy/paste and display (#10009)
## Summary

Replace `app.rootGraph.getNodeById()` with `resolveNode()` so node
lookups search subgraphs, fixing broken image copy/paste and display for
nodes inside subgraphs.

## Changes

- **What**: Updated 7 call sites across 5 files to use `resolveNode()`
from `litegraphUtil.ts` instead of `app.rootGraph.getNodeById()`. The
`resolveNode` function already existed and searches both the root graph
and all subgraphs. Added a unit test verifying subgraph node resolution
in `nodeOutputStore`.

## Review Focus

The fix is mechanical — each call site simply swaps
`app.rootGraph?.getNodeById(id)` for `resolveNode(id)`. The
`resolveNode` utility iterates `graph.subgraphs` if the node is not
found in the root graph.

Fixes #9993

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10009-fix-resolve-nodes-in-subgraphs-for-image-copy-paste-and-display-3256d73d3650814f9467c53999f5d755)
by [Unito](https://www.unito.io)
2026-03-17 06:58:01 -07:00
Kelly Yang
ca2d61f393 use getLoad3dAsync (#9520)
## Summary

Applying `useLoad3dService().getLoad3dAsync` to fix the broken 3D viewer
scene.

Fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/9495

## Screenshots 

before

<img width="3456" height="2168" alt="665d1b83bf8b23a9ddde1411a34c8df9"
src="https://github.com/user-attachments/assets/6cb190f4-ef13-4fd3-a0c5-2360f056da55"
/>


after

<img width="5120" height="2638" alt="image"
src="https://github.com/user-attachments/assets/154b1a98-bd71-41e2-839d-f0f1f7e5e72e"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9520-use-getLoad3dAsync-31c6d73d365081cf8c24cd89b545ccb4)
by [Unito](https://www.unito.io)
2026-03-07 09:22:35 -08:00
Christian Byrne
ef4e4a69d5 fix: enable enforce-consistent-class-order tailwind lint rule (#9428)
## Summary

Enable `better-tailwindcss/enforce-consistent-class-order` lint rule and
auto-fix all 1027 violations across 263 files. Stacked on #9427.

## Changes

- **What**: Sort Tailwind classes into consistent order via `eslint
--fix`
- Enable `enforce-consistent-class-order` as `'error'` in eslint config
- Purely cosmetic reordering — no behavioral or visual changes

## Review Focus

Mechanical auto-fix PR — all changes are class reordering only. This is
the largest diff but lowest risk since it changes no class names, only
their order.

**Stack:** #9417#9427 → **this PR**

Fixes #9300 (partial — 3 of 3 rules)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9428-fix-enable-enforce-consistent-class-order-tailwind-lint-rule-31a6d73d3650811c9065f5178ba3e724)
by [Unito](https://www.unito.io)
2026-03-05 17:24:34 -08:00
Christian Byrne
1221756e05 fix: enable enforce-canonical-classes tailwind lint rule (#9427)
## Summary

Enable `better-tailwindcss/enforce-canonical-classes` lint rule and
auto-fix all 611 violations across 173 files. Stacked on #9417.

## Changes

- **What**: Simplify Tailwind classes to canonical forms via `eslint
--fix`:
  - `h-X w-X` → `size-X`
  - `overflow-x-hidden overflow-y-hidden` → `overflow-hidden`
  - and other canonical simplifications
- Enable `enforce-canonical-classes` as `'error'` in eslint config

## Review Focus

Mechanical auto-fix PR — all changes produced by `eslint --fix`. No
visual or behavioral changes; canonical forms are functionally
identical.

**Stack:** #9417 → **this PR** → PR 3 (class order)

Fixes #9300 (partial — 2 of 3 rules)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9427-fix-enable-enforce-canonical-classes-tailwind-lint-rule-31a6d73d365081a49340d7d4640ede45)
by [Unito](https://www.unito.io)
2026-03-05 17:07:46 -08:00
Alexander Brown
69c8c84aef fix: resolve i18n no-restricted-imports lint warnings (#8704)
## Summary

Fix all i18n `no-restricted-imports` lint warnings and upgrade rules
from `warn` to `error`.

## Changes

- **What**: Migrate Vue components from `import { t/d } from '@/i18n'`
to `const { t } = useI18n()`. Migrate non-component `.ts` files from
`useI18n()` to `import { t/d } from '@/i18n'`. Allow `st` import from
`@/i18n` in Vue components (it wraps `te`/`t` for safe fallback
translation). Remove `@deprecated` tag from `i18n.ts` global exports
(still used by `st` and non-component code). Upgrade both lint rules
from `warn` to `error`.

## Review Focus

- The `st` helper is intentionally excluded from the Vue component
restriction since it provides safe fallback translation needed for
custom node definitions.

Fixes #8701

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8704-fix-resolve-i18n-no-restricted-imports-lint-warnings-2ff6d73d365081ae84d8eb0dfef24323)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-02-06 20:54:53 -08:00
Johnpaul Chiwetelu
90a701dd67 Road to No Explicit Any Part 11 (#8565)
## Summary

This PR removes `any` types from widgets, services, stores, and test
files, replacing them with proper TypeScript types.

### Key Changes

#### Type Safety Improvements
- Replaced `any` with `unknown`, explicit types, or proper interfaces
across widgets and services
- Added proper type imports (TgpuRoot, Point, StyleValue, etc.)
- Created typed interfaces (NumericWidgetOptions, TestWindow,
ImportFailureDetail, etc.)
- Fixed function return types to be non-nullable where appropriate
- Added type guards and null checks instead of non-null assertions
- Used `ComponentProps` from vue-component-type-helpers for component
testing

#### Widget System
- Added index signature to IWidgetOptions for Record compatibility
- Centralized disabled logic in WidgetInputNumberInput
- Moved template type assertions to computed properties
- Fixed ComboWidget getOptionLabel type assertions
- Improved remote widget type handling with runtime checks

#### Services & Stores
- Fixed getOrCreateViewer to return non-nullable values
- Updated addNodeOnGraph to use specific options type `{ pos?: Point }`
- Added proper type assertions for settings store retrieval
- Fixed executionIdToCurrentId return type (string | undefined)

#### Test Infrastructure
- Exported GraphOrSubgraph from litegraph barrel to avoid circular
dependencies
- Updated test fixtures with proper TypeScript types (TestInfo,
LGraphNode)
- Replaced loose Record types with ComponentProps in tests
- Added proper error handling in WebSocket fixture

#### Code Organization
- Created shared i18n-types module for locale data types
- Made ImportFailureDetail non-exported (internal use only)
- Added @public JSDoc tag to ElectronWindow type
- Fixed console.log usage in scripts to use allowed methods

### Files Changed

**Widgets & Components:**
-
src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
-
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDefault.vue
-
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
- src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue
-
src/renderer/extensions/vueNodes/widgets/composables/useRemoteWidget.ts
- src/lib/litegraph/src/widgets/ComboWidget.ts
- src/lib/litegraph/src/types/widgets.ts
- src/components/common/LazyImage.vue
- src/components/load3d/Load3dViewerContent.vue

**Services & Stores:**
- src/services/litegraphService.ts
- src/services/load3dService.ts
- src/services/colorPaletteService.ts
- src/stores/maskEditorStore.ts
- src/stores/nodeDefStore.ts
- src/platform/settings/settingStore.ts
- src/platform/workflow/management/stores/workflowStore.ts

**Composables & Utils:**
- src/composables/node/useWatchWidget.ts
- src/composables/useCanvasDrop.ts
- src/utils/widgetPropFilter.ts
- src/utils/queueDisplay.ts
- src/utils/envUtil.ts

**Test Files:**
- browser_tests/fixtures/ComfyPage.ts
- browser_tests/fixtures/ws.ts
- browser_tests/tests/actionbar.spec.ts
-
src/workbench/extensions/manager/components/manager/skeleton/PackCardGridSkeleton.test.ts
- src/lib/litegraph/src/subgraph/subgraphUtils.test.ts
- src/components/rightSidePanel/shared.test.ts
- src/platform/cloud/subscription/composables/useSubscription.test.ts
-
src/platform/workflow/persistence/composables/useWorkflowPersistence.test.ts

**Scripts & Types:**
- scripts/i18n-types.ts (new shared module)
- scripts/diff-i18n.ts
- scripts/check-unused-i18n-keys.ts
- src/workbench/extensions/manager/types/conflictDetectionTypes.ts
- src/types/algoliaTypes.ts
- src/types/simplifiedWidget.ts

**Infrastructure:**
- src/lib/litegraph/src/litegraph.ts (added GraphOrSubgraph export)
- src/lib/litegraph/src/infrastructure/CustomEventTarget.ts
- src/platform/assets/services/assetService.ts

**Stories:**
- apps/desktop-ui/src/views/InstallView.stories.ts
- src/components/queue/job/JobDetailsPopover.stories.ts

**Extension Manager:**
- src/workbench/extensions/manager/composables/useConflictDetection.ts
- src/workbench/extensions/manager/composables/useManagerQueue.ts
- src/workbench/extensions/manager/services/comfyManagerService.ts
- src/workbench/extensions/manager/utils/conflictMessageUtil.ts

### Testing

- [x] All TypeScript type checking passes (`pnpm typecheck`)
- [x] ESLint passes without errors (`pnpm lint`)
- [x] Format checks pass (`pnpm format:check`)
- [x] Knip (unused exports) passes (`pnpm knip`)
- [x] Pre-commit and pre-push hooks pass

Part of the "Road to No Explicit Any" initiative.

### Previous PRs in this series:
- Part 2: #7401
- Part 3: #7935
- Part 4: #7970
- Part 5: #8064
- Part 6: #8083
- Part 7: #8092
- Part 8 Group 1: #8253
- Part 8 Group 2: #8258
- Part 8 Group 3: #8304
- Part 8 Group 4: #8314
- Part 8 Group 5: #8329
- Part 8 Group 6: #8344
- Part 8 Group 7: #8459
- Part 8 Group 8: #8496
- Part 9: #8498
- Part 10: #8499

---------

Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-05 16:29:28 -08:00
Alexander Brown
cbdc7d030f feat: code splitting optimization - reduce initial bundle by 36% (#8542)
## Summary

Reduces initial module preload from 12.94 MB to 8.24 MB (-4.7 MB, -36%).

## Changes

- Split vendor chunks for better cache isolation (firebase, sentry,
i18n, zod, etc.)
- Exclude heavy optional chunks from initial preload: THREE.js, xterm,
tiptap, chart.js, yjs
- Lazy load 16 dialog components in dialogService
- Add \endor-yjs\ chunk for CRDT library

## Metrics

| Metric | Before | After |
|--------|--------|-------|
| Initial preload | 12.94 MB | 8.24 MB |
| vendor-other | 4,006 KB | 2,156 KB |
| dialogService | 1,860 KB | 1,304 KB |

All excluded chunks still load on-demand when their features are used.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8542-feat-code-splitting-optimization-reduce-initial-bundle-by-36-2fb6d73d36508146aaf7fdaed3274033)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-02 19:05:28 -08:00
Benjamin Lu
4e20b7522b Forward scroll unless focused (#6597)
## Summary

Forward wheel events to the canvas unless a wheel-capturing element is
focused, and ensure the Load3D scene becomes focusable on pointer
interaction so its wheel zoom/pan works after the user clicks into it.

## Changes

- **What**: gate wheel forwarding on focused capture elements; focus the
Load3D scene container on pointerdown to opt into wheel capture.
- **Dependencies**: none

## Review Focus

- Validate wheel forwarding behavior across focusable inputs vs.
non-focusable capture zones.
- Confirm Load3D zoom/pan only captures wheel after a user click (canvas
pan should still work when merely hovering).

## Screenshots (if applicable)

N/A

---------

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Subagent 5 <subagent@example.com>
Co-authored-by: Amp <amp@ampcode.com>
2026-02-01 09:32:10 -05:00
pythongosssss
d890e7568a Add asset deletion progress indicator (#7906)
## Summary

Currently the delay between user action and visual response is too long
and it looks like it hasn't worked.
This adds a visual indicator that the action is processing.

## Changes

- add loading indicator while asset is deleting

## Screenshots (if applicable)
(Artifically delayed deletion)


https://github.com/user-attachments/assets/a9a8b9fe-896d-4666-b643-ec8b990f6444

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7906-Add-asset-deletion-progress-indicator-2e26d73d365081ed82b8e770ba3d0615)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Jin Yi <jin12cc@gmail.com>
2026-01-27 21:21:50 -07:00
Johnpaul Chiwetelu
9e434a1002 fix: replace text-white with theme-aware color tokens (#7908)
## Summary
- Replace hardcoded `text-white` class with theme-aware alternatives to
fix invisible text on light themes
- Update Load3D control backgrounds to use semantic tokens
- Update dropdown menus to use `bg-interface-menu-surface`
- Update overlay backgrounds to use `bg-backdrop` with opacity

## Changes
| Component | Old | New |
|-----------|-----|-----|
| Text on primary bg | `text-white` | `text-base-foreground` |
| Dropdown menus | `bg-black/50` | `bg-interface-menu-surface` |
| Control panels | `bg-smoke-700/30` | `bg-backdrop/30` |
| Loading overlays | `bg-black bg-opacity-50` | `bg-backdrop/50` |
| Selected states | `bg-smoke-600` | `bg-button-active-surface` |

## Files Modified (14)
- `src/components/TopMenuSection.vue`
- `src/components/input/MultiSelect.vue`
- `src/components/load3d/*.vue` (12 files)
- `src/renderer/extensions/vueNodes/VideoPreview.vue`

## Test plan
- [ ] Verify text visibility in light theme
- [ ] Verify text visibility in dark theme
- [ ] Test Load3D viewer controls functionality
- [ ] Test MultiSelect dropdown checkbox visibility

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7908-fix-replace-text-white-with-theme-aware-color-tokens-2e26d73d36508107bb01d1d6e3b74f6a)
by [Unito](https://www.unito.io)
2026-01-09 02:40:15 +01:00
Terry Jia
dad1eafecc feat: add skeleton visualization toggle for 3D models (#7857)
## Summary

For better support animation 3d model custon node, such as
https://github.com/jtydhr88/ComfyUI-HY-Motion1, add ability to show/hide
skeleton bones in Load3D nodes for models with skeletal animation. Uses
THREE.SkeletonHelper with root bone detection to properly support both
FBX and GLB model formats.

## Screenshots


https://github.com/user-attachments/assets/df9de4a6-549e-4227-aa00-8859d71f43d1

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7857-feat-add-skeleton-visualization-toggle-for-3D-models-2e06d73d365081a39f49f81f72657a70)
by [Unito](https://www.unito.io)
2026-01-06 19:11:06 -07:00
Terry Jia
7fcfa4c201 feat: add animation progress bar for 3D nodes and viewer (#7839)
## Summary
- Add draggable progress bar to AnimationControls component
- Display current time / total duration
- Allow seeking through animations when paused or playing
- Add animation controls to 3D Viewer

fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/7830 and
https://github.com/Comfy-Org/ComfyUI_frontend/issues/7831

## Screenshots (if applicable)


https://github.com/user-attachments/assets/f6d0668c-c7a4-497e-8345-9ef6e47a41c6

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7839-feat-add-animation-progress-bar-for-3D-nodes-and-viewer-2de6d73d36508101ac98f673206b30d9)
by [Unito](https://www.unito.io)
2026-01-04 08:47:30 -05:00
Kelly Yang
8d1f8edc5a fix 3d-min-resize (#7815)
## Summary

Set up a minimum height on the viewer container to prevent layout
collapse caused by the absolutely positioned canvas.

## Changes

What: Updated handleResize apply a minimum height style to the parent
element

## Screenshots 
before


https://github.com/user-attachments/assets/0ddb2681-3083-4dfe-b59f-77724072002d

after


https://github.com/user-attachments/assets/672570ad-8792-4e09-8050-6dfcb921cd36

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7815-fix-3d-min-resize-2da6d73d3650811ca66fd82ad13258b9)
by [Unito](https://www.unito.io)
2026-01-03 18:39:28 -05:00
Alexander Brown
7b68b19f11 Component: The Rest of the PrimeVue buttons (#7649)
## Summary

Automated initial change, cleaned up manually.

Please check the screenshot changes.

Includes a11y updates to icon buttons.

Doesn't hit the buttons in Desktop.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7649-WIP-Component-The-Rest-of-the-PrimeVue-buttons-2ce6d73d365081d68e06f200f1321267)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-12-29 15:03:34 -08:00
Terry Jia
3c4b99ed84 3dgs & ply support (#7602)
## Summary

integrated sparkjs https://sparkjs.dev/, built by [world labs
](https://www.worldlabs.ai/) to support 3dgs.

- Add 3D Gaussian Splatting (3DGS) support using @sparkjsdev/spark
library
- Add PLY file format support with multiple rendering engines
- Support new file formats: `.ply`, `.spz`, `.splat`, `.ksplat`
- Add PLY Engine setting with three options: `threejs` (mesh), `fastply`
(optimized ASCII point clouds), `sparkjs` (3DGS)
- Add `FastPLYLoader` for 4-5x faster ASCII PLY parsing
- Add `original(Advanced)` material mode for point cloud rendering with
THREE.Points

3dgs generated by https://marble.worldlabs.ai/

test ply file from:
1. made by https://github.com/PozzettiAndrea/ComfyUI-DepthAnythingV3
2. threejs offically repo

## Screenshots


https://github.com/user-attachments/assets/44e64d3e-b58d-4341-9a70-a9aa64801220



https://github.com/user-attachments/assets/76b0dfba-0c12-4f64-91cb-bfc5d672294d



https://github.com/user-attachments/assets/2a8bfe81-1fb2-44c4-8787-dff325369c61



https://github.com/user-attachments/assets/e4beecee-d7a2-40c9-97f7-79b09c60312d

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7602-3dgs-ply-support-2cd6d73d3650814098fcea86cfaf747d)
by [Unito](https://www.unito.io)
2025-12-20 14:04:16 -07:00
Alexander Brown
db929220af Chore: Update several Develeoper Dependencies (#7590)
## Summary

Hopefully this will stabilize the precommit and prepush behavior for
developers using Windows and speed up the runtime of a few scripts for
everyone else.

Includes fixes for unused refs that were caught by the updated vue-tsc.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7590-Chore-Update-several-Develeoper-Dependencies-2cc6d73d365081fdb27cd00e53b169d5)
by [Unito](https://www.unito.io)
2025-12-17 15:24:07 -08:00
Alexander Brown
72b5444d5a Devex: Linter updates (#7309)
## Summary

Updates for the linter/formatter deps, turning on some more rules.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7309-WIP-Linter-updates-2c56d73d36508101b3ece6bcaf7e5212)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
2025-12-10 11:08:47 -08:00
Alexander Brown
497bafcaeb Fix: Widget sizing with multiple expanding items (#7118)
## Summary

Textarea/Markdown/3D pieces grow, other widget rows size to their
contents.

## Screenshots (if applicable)
<img width="564" height="420" alt="image"
src="https://github.com/user-attachments/assets/35aeb9bf-a44d-4e3f-b2cd-a9f3604a7778"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7118-Fix-Widget-sizing-with-multiple-expanding-items-2be6d73d3650816c8153ff3f1e49176d)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-12-02 18:49:45 -08:00
Terry Jia
a9f416233d feat: support open 3d viewer in media asset panel (#6703)
## Summary

Add support for previewing 3D assets directly in the Media Asset Panel.

## Changes

- **3D Asset Preview**: Clicking on 3D assets (`.glb`, `.gltf`, etc.) in
the Media Asset Panel now opens the full
  3D viewer

## Screenshots



https://github.com/user-attachments/assets/38808712-acc8-42aa-9f11-8d8bf2387b20

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6703-feat-support-open-3d-viewer-in-media-asset-panel-2ab6d73d3650811dbff9ecb570a0a878)
by [Unito](https://www.unito.io)
2025-11-14 14:31:58 -05:00
Terry Jia
812f1797d5 fix warning log (#6650)
## Summary

we need to add root div to avoid warning

> Extraneous non-props attributes (data-v-inspector) were passed to
> component but could not be automatically inherited because component
renders fragment or text or teleport root
> nodes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6650-fix-warning-log-2a86d73d365081a89c5af0c60cdaa618)
by [Unito](https://www.unito.io)
2025-11-11 11:45:11 -07:00
Terry Jia
879cb8f1a8 support panoramic image in 3d node (#6638)
## Summary

Adds panoramic image support to the 3D node viewer, allowing users to
display equirectangular panoramic images as immersive backgrounds
alongside the existing tiled image mode.

## Changes

- Toggle between tiled and panorama rendering modes for background
images
- Field of view (FOV) control for panorama mode
- Refactored FOV slider into reusable PopupSlider component

## Screenshots


https://github.com/user-attachments/assets/8955d74b-b0e6-4b26-83ca-ccf902b43aa6

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6638-support-panoramic-image-in-3d-node-2a56d73d365081b98647f988130e312e)
by [Unito](https://www.unito.io)
2025-11-11 04:02:12 -05:00
Terry Jia
afa10f7a1e [refactor] refactor load3d (#5765)
Summary

Fully Refactored the Load3D module to improve architecture and
maintainability by consolidating functionality into a
centralized composable pattern and simplifying component structure. and
support VueNodes system

  Changes

- Architecture: Introduced new useLoad3d composable to centralize 3D
loading logic and state
  management
- Component Simplification: Removed redundant components
(Load3DAnimation.vue, Load3DAnimationScene.vue,
  PreviewManager.ts) 
- Support VueNodes
- improve config store
- remove lineart output due Animation doesnot support it, may add it
back later
- remove Preview screen and keep scene in fixed ratio in load3d (not
affect preview3d)
- improve record video feature which will already record video by same
ratio as scene
Need BE change https://github.com/comfyanonymous/ComfyUI/pull/10025


https://github.com/user-attachments/assets/9e038729-84a0-45ad-b0f2-11c57d7e0c9a



┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5765-refactor-refactor-load3d-2796d73d365081728297cc486e2e9052)
by [Unito](https://www.unito.io)
2025-10-31 16:19:35 -04:00
Terry Jia
dd90288846 code improve for 3d node (#6403)
## Summary

code improve for 3d node

## Changes

1. remove custom css
2. use tailwind instead
3. bug fix for right click context menu

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6403-code-improve-for-3d-node-29c6d73d3650819fab26feabdaa21821)
by [Unito](https://www.unito.io)
2025-10-30 05:31:36 -04:00
Alexander Brown
b03cf7e11d Style: Token renaming and style organization (#6337)
## Summary

Align color names and organize style.css some more

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6337-Style-Token-renaming-and-style-organization-29a6d73d365081b69f25ce1298c67fdc)
by [Unito](https://www.unito.io)
2025-10-28 12:13:28 -07:00
sno
20a1a9eda2 [bugfix] fix @intlify/vue-i18n/no-raw-text linting errors (#6280)
## Summary

This PR fixes all @intlify/vue-i18n/no-raw-text linting errors
identified in #5625 by replacing raw text strings with proper i18n
translation function calls.

## Changes

Fixed i18n linting errors in the following files:
- `src/components/widget/SampleModelSelector.vue` - "Upload Model" →
`$t('g.upload')`
- `src/components/topbar/CurrentUserButton.vue` - "user profile" →
`$t('g.currentUser')`
- `src/components/sidebar/tabs/nodeLibrary/NodeHelpPage.vue` - "Loading
help" → `$t('g.loading')`
- `src/components/sidebar/SidebarShortcutsToggleButton.vue` -
"shortcuts.shortcuts" → `$t('shortcuts.shortcuts')`
- `src/components/sidebar/SidebarLogoutIcon.vue` - "sideToolbar.logout"
→ `$t('sideToolbar.logout')`
- `src/components/sidebar/SidebarHelpCenterIcon.vue` - "menu.help" →
`$t('menu.help')`
- `src/components/sidebar/SidebarBottomPanelToggleButton.vue` -
"sideToolbar.labels.console" → `$t('sideToolbar.labels.console')`
- `src/components/load3d/controls/viewer/ViewerCameraControls.vue` -
"fov" → `t('load3d.fov')`
- `src/components/helpcenter/HelpCenterMenuContent.vue` - "Help Center
Menu" and "Recent releases" → `$t()` calls

All raw text strings have been replaced with appropriate i18n
translation keys that already exist in `src/locales/en/main.json`.

## Related Issue

Fixes errors reported in CI job:
https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/18705105609/job/53341658467?pr=5625

This PR aims to help #5625 pass CI/CD checks.

## Test Plan

- All i18n linting errors should be resolved
- No functionality changes - only proper use of i18n system
- Existing translation keys are used from the locale files

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6280-bugfix-fix-intlify-vue-i18n-no-raw-text-linting-errors-2976d73d365081369b43de01486fb409)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2025-10-26 22:12:45 -07:00
Alexander Brown
b943c0fa75 Lint: Add tailwind linter (#5984)
## Summary

Adds the [tailwind lint
plugin](https://github.com/francoismassart/eslint-plugin-tailwindcss/?tab=readme-ov-file#eslint-plugin-tailwindcss)
and fixes the currently fixable rules ([v4 is still in
beta](https://github.com/francoismassart/eslint-plugin-tailwindcss/?tab=readme-ov-file#about-tailwind-css-4-support)).

## Changes

- **What**: Enforces things like consistent class order, and eventually
can prohibit extra classes that could be utilities instead
- **Dependencies**: The plugin and its types

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5984-Lint-Add-tailwind-linter-2866d73d365081d89db0d998232533bb)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2025-10-08 19:39:14 -07:00
Alexander Brown
08220d50d9 Lint: Turn on rules that should allow for verbatimModuleSyntax (#5616)
* lint: turn on type import rules setting up for verbatimModuleSyntax

* lint: --fix for type imports
2025-09-16 22:03:41 -07:00
Christian Byrne
27ab355f9c [refactor] Improve updates/notifications domain organization (#5590)
* [refactor] Move update-related functionality to platform/updates domain

Reorganizes release management, version compatibility, and notification functionality
following Domain-Driven Design principles, mirroring VSCode's architecture pattern.

- Move releaseService.ts to platform/updates/common/
- Move releaseStore.ts to platform/updates/common/
- Move versionCompatibilityStore.ts to platform/updates/common/
- Move useFrontendVersionMismatchWarning.ts to platform/updates/common/
- Move toastStore.ts to platform/updates/common/
- Move ReleaseNotificationToast.vue to platform/updates/components/
- Move WhatsNewPopup.vue to platform/updates/components/
- Update 25+ import paths across codebase and tests

This creates a cohesive "updates" domain containing all functionality related to
software updates, version checking, release notifications, and user communication
about application state changes.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix imports

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-15 04:34:37 -07:00
Christian Byrne
4c8c4a1ad4 [refactor] Improve settings domain organization (#5550)
* refactor: move settingStore to platform/settings

Move src/stores/settingStore.ts to src/platform/settings/settingStore.ts
to separate platform infrastructure from domain logic following DDD principles.

Updates all import references across ~70 files to maintain compatibility.

* fix: update remaining settingStore imports after rebase

* fix: complete remaining settingStore import updates

* fix: update vi.mock paths for settingStore in tests

Update all test files to mock the new settingStore location at
@/platform/settings/settingStore instead of @/stores/settingStore

* fix: resolve remaining settingStore imports and unused imports after rebase

* fix: update settingStore mock path in SelectionToolbox test

Fix vi.mock path from @/stores/settingStore to @/platform/settings/settingStore
to resolve failing Load3D viewer button test.

* refactor: complete comprehensive settings migration to platform layer

This commit completes the migration of all settings-related code to the platform layer
as part of the Domain-Driven Design (DDD) architecture refactoring.

- constants/coreSettings.ts → platform/settings/constants/coreSettings.ts
- types/settingTypes.ts → platform/settings/types.ts
- stores/settingStore.ts → platform/settings/settingStore.ts (already moved)

- composables/setting/useSettingUI.ts → platform/settings/composables/useSettingUI.ts
- composables/setting/useSettingSearch.ts → platform/settings/composables/useSettingSearch.ts
- composables/useLitegraphSettings.ts → platform/settings/composables/useLitegraphSettings.ts

- components/dialog/content/SettingDialogContent.vue → platform/settings/components/SettingDialogContent.vue
- components/dialog/content/setting/SettingItem.vue → platform/settings/components/SettingItem.vue
- components/dialog/content/setting/SettingGroup.vue → platform/settings/components/SettingGroup.vue
- components/dialog/content/setting/SettingsPanel.vue → platform/settings/components/SettingsPanel.vue
- components/dialog/content/setting/ColorPaletteMessage.vue → platform/settings/components/ColorPaletteMessage.vue
- components/dialog/content/setting/ExtensionPanel.vue → platform/settings/components/ExtensionPanel.vue
- components/dialog/content/setting/ServerConfigPanel.vue → platform/settings/components/ServerConfigPanel.vue

- ~100+ import statements updated across the codebase
- Test file imports corrected
- Component imports fixed in dialog service and command menubar
- Composable imports updated in GraphCanvas.vue

```
src/platform/settings/
├── components/          # All settings UI components
├── composables/         # Settings-related composables
├── constants/          # Core settings definitions
├── types.ts           # Settings type definitions
└── settingStore.ts    # Central settings state management
```

 TypeScript compilation successful
 All tests passing (settings store, search functionality, UI components)
 Production build successful
 Domain boundaries properly established

This migration consolidates all settings functionality into a cohesive platform domain,
improving maintainability and following DDD principles for better code organization.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: format and lint after rebase conflict resolution

* fix: update remaining import paths to platform settings

- Fix browser test import: extensionAPI.spec.ts
- Fix script import: collect-i18n-general.ts
- Complete settings migration import path updates

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-15 03:53:08 -07:00