Skip to content

Conversation

@kaili-yang
Copy link
Contributor

@kaili-yang kaili-yang commented Jan 4, 2026

Summary

Enable the rich WidgetSelectDropdown component for the model_file input on "Load 3D" nodes in Vue (Nodes 2.0) mode. This will provide grid/list views, search, and upload capabilities directly in the dropdown, matching the "Load Image" node experience.

Changes

  • What: The core logic is in the src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue and src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue

Review Focus

Drag and drop a 3D file onto the dropdown (or use upload button if enabled in dropdown).

Screenshots

updata.3d.widget-after.mov

┆Issue is synchronized with this Notion page by Unito

@kaili-yang kaili-yang requested a review from a team as a code owner January 4, 2026 19:58
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jan 4, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Renames model→mesh across 3D handling, adds uploadSubfolder '3d', extends upload/accept types for meshes, deduplicates and caps saved uploads to 12 (localStorage trimming), updates load flow to use meshUrl and load3d.loadModel, and adds error logging/toasts.

Changes

Cohort / File(s) Summary
Load3D configuration
src/extensions/core/load3d/Load3dConfiguration.ts
Public setting renamed modelWidgetmeshWidget; internal handlers renamed to mesh equivalents; filters meshWidget.options.values using Comfy.Load3D.HiddenFiles from localStorage with try/catch and truncates to 12.
Core Load3D logic
src/extensions/core/load3d.ts
All references moved from modelWidget/modelUrl to meshWidget/meshUrl; upload/clear/node init/serialization operate on mesh paths; upload failures show fileUploadFailed toast and log.
Composable: load3d usage
src/composables/useLoad3d.ts
Replaced model URL/flows with mesh equivalents (getMeshUrl, load3d.loadModel(meshUrl)), preserved camera restore and loading/error handling but adjusted messages/logs to mesh terminology.
Widget select & descriptor
src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
Detects Load3D mesh (nodeType==='Load3D' && widget.name==='model_file'), marks assetKind: 'mesh', adds subfolder: '3d' to descriptor, exposes uploadSubfolder prop, and sets default layout for meshes.
Widget select dropdown / upload
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
Adds uploadSubfolder?: string prop; adds mesh support (accept .obj,.stl,.ply,.spz, placeholderModel); uploadFile sends optional subfolder; after upload, reverses uploaded paths, deduplicates against widget.options.values, prepends new entries, trims to 12, and sets value to latest.
Dropdown placeholder UI
src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
Replaced previous gradient no-media placeholder with a centered slate block rendering an inline pi-box icon.
Types
src/types/widgetTypes.ts
AssetKind variant renamed from 'model' to 'mesh' (`'image'

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant WidgetSelect
    participant WidgetSelectDropdown
    participant UploadHandler
    participant Backend
    participant AppState
    participant Load3D

    User->>WidgetSelect: open/select model_file widget
    WidgetSelect->>WidgetSelectDropdown: pass uploadSubfolder="3d"
    User->>WidgetSelectDropdown: select/upload file(s)
    WidgetSelectDropdown->>UploadHandler: uploadFile(files, subfolder="3d")
    UploadHandler->>Backend: POST files + subfolder="3d"
    Backend-->>UploadHandler: return uploaded paths

    rect rgba(230,245,255,0.9)
      UploadHandler-->>WidgetSelectDropdown: uploaded paths
      WidgetSelectDropdown->>WidgetSelectDropdown: reverse, dedupe vs options.values
      WidgetSelectDropdown->>WidgetSelectDropdown: prepend new paths, trim to 12
      WidgetSelectDropdown->>AppState: update widget.options.values and modelValue
    end

    alt upload succeeded
      WidgetSelectDropdown->>Load3D: notify widget callback / change
      Load3D->>Load3D: compute meshUrl from widget value
      Load3D->>Load3D: set loading state and call load3d.loadModel(meshUrl)
      Load3D-->>AppState: restore camera if present
      Load3D->>AppState: clear loading state
    else upload failed
      WidgetSelectDropdown->>AppState: show fileUploadFailed toast
    end
Loading

Possibly related PRs

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f47ddb0 and ca48b27.

📒 Files selected for processing (1)
  • src/composables/useLoad3d.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Jan 4, 2026

🎭 Playwright Test Results

loading Tests are starting...

⏰ Started at: 01/06/2026, 04:12:48 AM UTC

🚀 Running Tests

  • 🧪 chromium: Running tests...
  • 🧪 chromium-0.5x: Running tests...
  • 🧪 chromium-2x: Running tests...
  • 🧪 mobile-chrome: Running tests...

⏱️ Please wait while tests are running...

@github-actions
Copy link

github-actions bot commented Jan 4, 2026

🎨 Storybook Build Status

loading Build is starting...

⏰ Started at: 01/06/2026, 04:12:49 AM UTC

🚀 Building Storybook

  • 📦 Installing dependencies...
  • 🔧 Building Storybook components...
  • 🌐 Preparing deployment to Cloudflare Pages...

⏱️ Please wait while the Storybook build is in progress...

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fcfa4c and 2afd363.

📒 Files selected for processing (4)
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
🧰 Additional context used
📓 Path-based instructions (11)
src/**/*.vue

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.vue: Use the Vue 3 Composition API instead of the Options API when writing Vue components (exception: when overriding or extending PrimeVue components for compatibility)
Use setup() function for component logic
Utilize ref and reactive for reactive state
Implement computed properties with computed()
Use watch and watchEffect for side effects
Implement lifecycle hooks with onMounted, onUpdated, etc.
Utilize provide/inject for dependency injection
Use vue 3.5 style of default prop declaration
Use Tailwind CSS for styling
Implement proper props and emits definitions
Utilize Vue 3's Teleport component when needed
Use Suspense for async components
Follow Vue 3 style guide and naming conventions

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
src/**/*.{vue,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.{vue,ts}: Leverage VueUse functions for performance-enhancing styles
Implement proper error handling
Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/*.{ts,tsx,vue}: Sanitize HTML with DOMPurify to prevent XSS attacks
Avoid using @ts-expect-error; use proper TypeScript types instead
Use es-toolkit for utility functions instead of other utility libraries
Implement proper TypeScript types throughout the codebase

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
src/**/{composables,components}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Clean up subscriptions in state management to prevent memory leaks

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
src/**/*.{vue,ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
src/**/{components,composables}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Use vue-i18n for ALL user-facing strings by adding them to src/locales/en/main.json

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

**/*.vue: Use Vue 3.5+ with TypeScript in .vue files, exclusively using Composition API with <script setup lang="ts"> syntax
Use Tailwind 4 for styling in Vue components; avoid <style> blocks
Name Vue components using PascalCase (e.g., MenuHamburger.vue)
Use Vue 3.5 TypeScript-style default prop declaration with reactive props destructuring; do not use withDefaults or runtime props declaration
Prefer computed() over ref with watch when deriving values
Prefer useModel over separately defining prop and emit for two-way binding
Use vue-i18n in composition API for string literals; place new translation entries in src/locales/en/main.json
Use cn() utility function from @/utils/tailwindUtil for merging Tailwind class names; do not use :class="[]" syntax
Do not use the dark: Tailwind variant; use semantic values from the style.css theme instead (e.g., bg-node-component-surface)
Do not use !important or the ! important prefix for Tailwind classes; find and correct interfering !important classes instead
Avoid new usage of PrimeVue components; use VueUse, shadcn/vue, or Reka UI instead
Leverage VueUse functions for performance-enhancing styles in Vue components
Implement proper props and emits definitions in Vue components
Utilize Vue 3's Teleport component when needed
Use Suspense for async components
Follow Vue 3 style guide and naming conventions

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,vue}: Use TypeScript exclusively; do not write new JavaScript code
Use sorted and grouped imports organized by plugin/source
Enforce ESLint rules including Vue + TypeScript rules, disallow floating promises, disallow unused imports, and restrict i18n raw text in templates
Do not use any type or as any type assertions; fix the underlying type issue instead
Write code that is expressive and self-documenting; avoid redundant comments and clean as you go
Keep functions short and functional; minimize nesting and follow the arrow anti-pattern
Avoid mutable state; prefer immutability and assignment at point of declaration
Use function declarations instead of function expressions when possible
Use es-toolkit for utility functions
Implement proper error handling in code

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
**/*.{ts,tsx,vue,js,jsx,json,css}

📄 CodeRabbit inference engine (AGENTS.md)

Apply Prettier formatting with 2-space indentation, single quotes, no trailing semicolons, and 80-character line width

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
src/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.ts: Use es-toolkit for utility functions
Use TypeScript for type safety

Files:

  • src/extensions/core/load3d/Load3DConfiguration.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Minimize the surface area (exported values) of each module and composable

Files:

  • src/extensions/core/load3d/Load3DConfiguration.ts
🧠 Learnings (15)
📚 Learning: 2025-12-09T03:49:52.828Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 6300
File: src/platform/updates/components/WhatsNewPopup.vue:5-13
Timestamp: 2025-12-09T03:49:52.828Z
Learning: In Vue files across the ComfyUI_frontend repo, when a button is needed, prefer the repo's common button components from src/components/button/ (IconButton.vue, TextButton.vue, IconTextButton.vue) over plain HTML <button> elements. These components wrap PrimeVue with the project’s design system styling. Use only the common button components for consistency and theming, and import them from src/components/button/ as needed.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-09T21:40:12.361Z
Learnt from: benceruleanlu
Repo: Comfy-Org/ComfyUI_frontend PR: 7297
File: src/components/actionbar/ComfyActionbar.vue:33-43
Timestamp: 2025-12-09T21:40:12.361Z
Learning: In Vue single-file components, allow inline Tailwind CSS class strings for static classes and avoid extracting them into computed properties solely for readability. Prefer keeping static class names inline for simplicity and performance. For dynamic or conditional classes, use Vue bindings (e.g., :class) to compose classes.

Applies to all Vue files in the repository (e.g., src/**/*.vue) where Tailwind utilities are used for static styling.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-16T22:26:49.463Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7537
File: src/components/ui/button/Button.vue:17-17
Timestamp: 2025-12-16T22:26:49.463Z
Learning: In Vue 3.5+ with <script setup>, when using defineProps<Props>() with partial destructuring (e.g., const { as = 'button', class: customClass = '' } = defineProps<Props>() ), props that are not destructured (e.g., variant, size) stay accessible by name in the template scope. This pattern is valid: you can destructure only a subset of props for convenience while referencing the remaining props directly in template expressions. Apply this guideline to Vue components across the codebase (all .vue files).

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-22T21:36:08.369Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7649
File: src/platform/cloud/subscription/components/PricingTable.vue:185-201
Timestamp: 2025-12-22T21:36:08.369Z
Learning: In Vue components, avoid creating single-use variants for common UI components (e.g., Button and other shared components). Aim for reusable variants that cover multiple use cases. It’s acceptable to temporarily mix variant props with inline Tailwind classes when a styling need is unique to one place, but plan and consolidate into shared, reusable variants as patterns emerge across the codebase.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-11T03:55:51.755Z
Learnt from: simula-r
Repo: Comfy-Org/ComfyUI_frontend PR: 7252
File: src/renderer/extensions/vueNodes/components/ImagePreview.vue:151-158
Timestamp: 2025-12-11T03:55:51.755Z
Learning: In Vue components under src/renderer/extensions/vueNodes (e.g., ImagePreview.vue and LGraphNode.vue), implement image gallery keyboard navigation so that it responds to the node's focus state rather than requiring focus inside the image preview wrapper. Achieve this by wiring keyEvent handling at the node focus level and injecting or propagating key events (arrow keys) to the gallery when the node is focused/selected. This improves accessibility and aligns navigation with node-level focus behavior.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-11T12:25:15.470Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7358
File: src/components/dialog/content/signin/SignUpForm.vue:45-54
Timestamp: 2025-12-11T12:25:15.470Z
Learning: This repository uses CI automation to format code (pnpm format). Do not include manual formatting suggestions in code reviews for Comfy-Org/ComfyUI_frontend. If formatting issues are detected, rely on the CI formatter or re-run pnpm format. Focus reviews on correctness, readability, performance, accessibility, and maintainability rather than style formatting.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-18T02:07:38.870Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7598
File: src/components/sidebar/tabs/AssetsSidebarTab.vue:131-131
Timestamp: 2025-12-18T02:07:38.870Z
Learning: Tailwind CSS v4 safe utilities (e.g., items-center-safe, justify-*-safe, place-*-safe) are allowed in Vue components under src/ and in story files. Do not flag these specific safe variants as invalid when reviewing code in src/**/*.vue or related stories.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-18T21:15:46.862Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7603
File: src/components/queue/QueueOverlayHeader.vue:49-59
Timestamp: 2025-12-18T21:15:46.862Z
Learning: In the ComfyUI_frontend repository, for Vue components, do not add aria-label to buttons that have visible text content (e.g., buttons containing <span> text). The visible text provides the accessible name. Use aria-label only for elements without visible labels (e.g., icon-only buttons). If a button has no visible label, provide a clear aria-label or associate with an aria-labelledby describing its action.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-21T01:06:02.786Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7649
File: src/components/graph/selectionToolbox/ColorPickerButton.vue:15-18
Timestamp: 2025-12-21T01:06:02.786Z
Learning: In Comfy-Org/ComfyUI_frontend, in Vue component files, when a filled icon is required (e.g., 'pi pi-circle-fill'), you may mix PrimeIcons with Lucide icons since Lucide lacks filled variants. This mixed usage is acceptable when one icon library does not provide an equivalent filled icon. Apply consistently across Vue components in the src directory where icons are used, and document the rationale when a mixed approach is chosen.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-09T03:39:54.501Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7169
File: src/platform/remote/comfyui/jobs/jobTypes.ts:1-107
Timestamp: 2025-12-09T03:39:54.501Z
Learning: In the ComfyUI_frontend project, Zod is on v3.x. Do not suggest Zod v4 standalone validators (z.uuid, z.ulid, z.cuid2, z.nanoid) until an upgrade to Zod 4 is performed. When reviewing TypeScript files (e.g., src/platform/remote/comfyui/jobs/jobTypes.ts) validate against Zod 3 capabilities and avoid introducing v4-specific features; flag any proposal to upgrade or incorporate v4-only validators and propose staying with compatible 3.x patterns.

Applied to files:

  • src/extensions/core/load3d/Load3DConfiguration.ts
📚 Learning: 2025-12-13T11:03:11.264Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7416
File: src/stores/imagePreviewStore.ts:5-7
Timestamp: 2025-12-13T11:03:11.264Z
Learning: In the ComfyUI_frontend repository, lint rules require keeping 'import type' statements separate from non-type imports, even if importing from the same module. Do not suggest consolidating them into a single import statement. Ensure type imports remain on their own line (import type { ... } from 'module') and regular imports stay on separate lines.

Applied to files:

  • src/extensions/core/load3d/Load3DConfiguration.ts
📚 Learning: 2025-12-17T00:40:09.635Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7537
File: src/components/ui/button/Button.stories.ts:45-55
Timestamp: 2025-12-17T00:40:09.635Z
Learning: Prefer pure function declarations over function expressions (e.g., use function foo() { ... } instead of const foo = () => { ... }) for pure functions in the repository. Function declarations are more functional-leaning, offer better hoisting clarity, and can improve readability and tooling consistency. Apply this guideline across TypeScript files in Comfy-Org/ComfyUI_frontend, including story and UI component code, except where a function expression is semantically required (e.g., callbacks, higher-order functions with closures).

Applied to files:

  • src/extensions/core/load3d/Load3DConfiguration.ts
📚 Learning: 2025-12-30T22:22:33.836Z
Learnt from: kaili-yang
Repo: Comfy-Org/ComfyUI_frontend PR: 7805
File: src/composables/useCoreCommands.ts:439-439
Timestamp: 2025-12-30T22:22:33.836Z
Learning: When accessing reactive properties from Pinia stores in TypeScript files, avoid using .value on direct property access (e.g., useStore().isOverlayExpanded). Pinia auto-wraps refs when accessed directly, returning the primitive value. The .value accessor is only needed when destructuring store properties or when using storeToRefs().

Applied to files:

  • src/extensions/core/load3d/Load3DConfiguration.ts
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue Dropdown component with Select

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue OverlayPanel component with Popover

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
🔇 Additional comments (2)
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue (1)

202-206: LGTM: 3D model file type handling.

The addition of accept types for 3D model files (.obj,.stl,.ply,.spz) is appropriate and aligns well with the Load3D functionality.

src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue (1)

64-110: LGTM: 3D model handling logic.

The implementation correctly:

  • Detects Load3D nodes with model_file widget
  • Configures the descriptor with appropriate kind, allowUpload, folder, and subfolder values
  • Uses a sensible default subfolder ('3d') for organizing 3D model assets
  • Overrides folder to 'input' which aligns with backend expectations

The logic is clean, specific, and well-integrated with the existing asset handling system.

Comment on lines 43 to 44
if (setting.modelWidget.options?.values) {
let values = setting.modelWidget.options.values as string[]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add type guard before casting to string[].

The code casts setting.modelWidget.options.values to string[] without verifying that it's actually an array of strings. This could cause runtime errors if the values contain non-string elements.

🔎 Proposed fix with type guard
 if (setting.modelWidget.options?.values) {
-  let values = setting.modelWidget.options.values as string[]
+  const rawValues = setting.modelWidget.options.values
+  if (!Array.isArray(rawValues)) {
+    return
+  }
+  let values = rawValues.filter((v): v is string => typeof v === 'string')
🤖 Prompt for AI Agents
In src/extensions/core/load3d/Load3DConfiguration.ts around lines 43-44, the
code blindly casts setting.modelWidget.options.values to string[]; add a type
guard to ensure values is an array of strings before casting: check
Array.isArray(setting.modelWidget.options.values) and that every element has
typeof === "string" (or filter/map to coerce only string elements), then assign
to values; if the guard fails, handle gracefully (e.g., skip processing or
default to an empty array) instead of casting directly.

Comment on lines 115 to +120
<div
v-else
class="size-full bg-gradient-to-tr from-blue-400 via-teal-500 to-green-400"
/>
class="size-full flex items-center justify-center bg-slate-50"
>
<i class="pi pi-box text-slate-400" style="font-size: 2.5rem" />
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Replace hardcoded colors with semantic theme values.

The placeholder uses hardcoded Tailwind colors (bg-slate-50, text-slate-400) and an inline style for font size. Per coding guidelines, use semantic theme values from style.css instead of hardcoded colors, and prefer Tailwind utility classes over inline styles.

🔎 Proposed refactor using semantic values
       <div
         v-else
-        class="size-full flex items-center justify-center bg-slate-50"
+        class="size-full flex items-center justify-center bg-component-node-widget-background"
       >
-        <i class="pi pi-box text-slate-400" style="font-size: 2.5rem" />
+        <i class="pi pi-box text-[2.5rem] text-muted-foreground" />
       </div>

Note: Verify that bg-component-node-widget-background and text-muted-foreground are the appropriate semantic tokens in your theme. Adjust based on the actual theme definitions in style.css.

As per coding guidelines: "Do not use the dark: Tailwind variant; use semantic values from the style.css theme instead (e.g., bg-node-component-surface)."

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
lines 115-120, replace hardcoded Tailwind color classes and the inline font-size
with semantic theme values and Tailwind utilities: remove bg-slate-50 and
text-slate-400 and use the appropriate semantic classes from style.css (e.g.,
bg-component-node-widget-background and text-muted-foreground), and replace the
inline style="font-size: 2.5rem" with a Tailwind font-size utility (e.g.,
text-[2.5rem] or the nearest predefined text-3xl/text-4xl) so the component uses
theme tokens and utility classes instead of hardcoded colors and inline styles.

Comment on lines 299 to 311
const values = props.widget.options.values as string[]
uploadedPaths.reverse().forEach((path) => {
const existingIndex = values.indexOf(path)
if (existingIndex > -1) {
values.splice(existingIndex, 1)
}
values.unshift(path)
})
if (values.length > 12) {
values.splice(12)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add type guard and simplify array manipulation.

The code has two concerns:

  1. Type safety: Casts props.widget.options.values to string[] without verifying it's actually an array of strings (similar issue as in Load3DConfiguration.ts).
  2. Efficiency: The reverse().forEach() with splice and unshift operations results in O(n²) complexity.
🔎 Proposed fix with type guard and cleaner logic
     if (props.widget.options?.values) {
-      const values = props.widget.options.values as string[]
+      const rawValues = props.widget.options.values
+      if (!Array.isArray(rawValues)) {
+        return
+      }
+      
+      const values = rawValues.filter((v): v is string => typeof v === 'string')
+      const uploadedSet = new Set(uploadedPaths)
+      const filtered = values.filter((v) => !uploadedSet.has(v))
+      const updated = [...uploadedPaths, ...filtered].slice(0, 12)

-      uploadedPaths.reverse().forEach((path) => {
-        const existingIndex = values.indexOf(path)
-        if (existingIndex > -1) {
-          values.splice(existingIndex, 1)
-        }
-        values.unshift(path)
-      })
-
-      if (values.length > 12) {
-        values.splice(12)
-      }
+      props.widget.options.values = updated
     }

This approach:

  • Adds proper type validation
  • Eliminates O(n²) operations
  • Is more readable and functional
  • Maintains the same deduplication and ordering behavior
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const values = props.widget.options.values as string[]
uploadedPaths.reverse().forEach((path) => {
const existingIndex = values.indexOf(path)
if (existingIndex > -1) {
values.splice(existingIndex, 1)
}
values.unshift(path)
})
if (values.length > 12) {
values.splice(12)
}
const rawValues = props.widget.options.values
if (!Array.isArray(rawValues)) {
return
}
const values = rawValues.filter((v): v is string => typeof v === 'string')
const uploadedSet = new Set(uploadedPaths)
const filtered = values.filter((v) => !uploadedSet.has(v))
const updated = [...uploadedPaths, ...filtered].slice(0, 12)
props.widget.options.values = updated
🤖 Prompt for AI Agents
In src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
around lines 299-311, add a type guard to ensure props.widget.options.values is
actually an array of strings (fall back to an empty array if not), then replace
the reverse+splice+unshift O(n²) loop with a linear approach: build a new array
by first taking uploadedPaths (filtered to strings and preserving their original
order), then append the existing values that are not in the uploadedPaths set to
avoid duplicates, finally truncate the resulting array to 12 entries and assign
it back to props.widget.options.values; this preserves behavior, ensures type
safety, and reduces complexity to O(n).

@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jan 4, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue (1)

312-327: Address the outstanding type safety and performance concerns.

This code segment was previously flagged for type safety issues and O(n²) complexity, but the concerns have not been addressed. The issues remain:

  1. Type Safety: Line 312 casts to string[] without verifying the array actually contains strings, which could cause runtime errors.
  2. Performance: The reverse().forEach() with indexOf/splice/unshift pattern results in O(n²) complexity that could be noticeable with larger lists.

The previous review suggested adding a type guard and replacing the loop with a linear-time approach using a Set for deduplication. Please refer to the detailed solution in the previous review comments.

src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue (1)

119-124: Address the outstanding theme token usage concerns.

This code segment was previously flagged for using hardcoded Tailwind colors (bg-slate-50, text-slate-400) and an inline style instead of semantic theme values from style.css, but the concerns have not been addressed.

Per coding guidelines: "Use semantic values from the style.css theme instead (e.g., bg-node-component-surface)."

Please refer to the detailed solution in the previous review comments, which suggested replacing these with semantic tokens like bg-component-node-widget-background and text-muted-foreground, and using a Tailwind utility class instead of the inline font-size style.

As per coding guidelines: semantic theme values should be used for all color and styling instead of hardcoded Tailwind colors.

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2afd363 and d7893c1.

📒 Files selected for processing (2)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
🧰 Additional context used
📓 Path-based instructions (9)
src/**/*.vue

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.vue: Use the Vue 3 Composition API instead of the Options API when writing Vue components (exception: when overriding or extending PrimeVue components for compatibility)
Use setup() function for component logic
Utilize ref and reactive for reactive state
Implement computed properties with computed()
Use watch and watchEffect for side effects
Implement lifecycle hooks with onMounted, onUpdated, etc.
Utilize provide/inject for dependency injection
Use vue 3.5 style of default prop declaration
Use Tailwind CSS for styling
Implement proper props and emits definitions
Utilize Vue 3's Teleport component when needed
Use Suspense for async components
Follow Vue 3 style guide and naming conventions

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
src/**/*.{vue,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.{vue,ts}: Leverage VueUse functions for performance-enhancing styles
Implement proper error handling
Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/*.{ts,tsx,vue}: Sanitize HTML with DOMPurify to prevent XSS attacks
Avoid using @ts-expect-error; use proper TypeScript types instead
Use es-toolkit for utility functions instead of other utility libraries
Implement proper TypeScript types throughout the codebase

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
src/**/{composables,components}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Clean up subscriptions in state management to prevent memory leaks

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
src/**/*.{vue,ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
src/**/{components,composables}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Use vue-i18n for ALL user-facing strings by adding them to src/locales/en/main.json

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

**/*.vue: Use Vue 3.5+ with TypeScript in .vue files, exclusively using Composition API with <script setup lang="ts"> syntax
Use Tailwind 4 for styling in Vue components; avoid <style> blocks
Name Vue components using PascalCase (e.g., MenuHamburger.vue)
Use Vue 3.5 TypeScript-style default prop declaration with reactive props destructuring; do not use withDefaults or runtime props declaration
Prefer computed() over ref with watch when deriving values
Prefer useModel over separately defining prop and emit for two-way binding
Use vue-i18n in composition API for string literals; place new translation entries in src/locales/en/main.json
Use cn() utility function from @/utils/tailwindUtil for merging Tailwind class names; do not use :class="[]" syntax
Do not use the dark: Tailwind variant; use semantic values from the style.css theme instead (e.g., bg-node-component-surface)
Do not use !important or the ! important prefix for Tailwind classes; find and correct interfering !important classes instead
Avoid new usage of PrimeVue components; use VueUse, shadcn/vue, or Reka UI instead
Leverage VueUse functions for performance-enhancing styles in Vue components
Implement proper props and emits definitions in Vue components
Utilize Vue 3's Teleport component when needed
Use Suspense for async components
Follow Vue 3 style guide and naming conventions

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,vue}: Use TypeScript exclusively; do not write new JavaScript code
Use sorted and grouped imports organized by plugin/source
Enforce ESLint rules including Vue + TypeScript rules, disallow floating promises, disallow unused imports, and restrict i18n raw text in templates
Do not use any type or as any type assertions; fix the underlying type issue instead
Write code that is expressive and self-documenting; avoid redundant comments and clean as you go
Keep functions short and functional; minimize nesting and follow the arrow anti-pattern
Avoid mutable state; prefer immutability and assignment at point of declaration
Use function declarations instead of function expressions when possible
Use es-toolkit for utility functions
Implement proper error handling in code

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
**/*.{ts,tsx,vue,js,jsx,json,css}

📄 CodeRabbit inference engine (AGENTS.md)

Apply Prettier formatting with 2-space indentation, single quotes, no trailing semicolons, and 80-character line width

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
🧠 Learnings (15)
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue Dropdown component with Select

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-09T03:49:52.828Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 6300
File: src/platform/updates/components/WhatsNewPopup.vue:5-13
Timestamp: 2025-12-09T03:49:52.828Z
Learning: In Vue files across the ComfyUI_frontend repo, when a button is needed, prefer the repo's common button components from src/components/button/ (IconButton.vue, TextButton.vue, IconTextButton.vue) over plain HTML <button> elements. These components wrap PrimeVue with the project’s design system styling. Use only the common button components for consistency and theming, and import them from src/components/button/ as needed.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-09T21:40:12.361Z
Learnt from: benceruleanlu
Repo: Comfy-Org/ComfyUI_frontend PR: 7297
File: src/components/actionbar/ComfyActionbar.vue:33-43
Timestamp: 2025-12-09T21:40:12.361Z
Learning: In Vue single-file components, allow inline Tailwind CSS class strings for static classes and avoid extracting them into computed properties solely for readability. Prefer keeping static class names inline for simplicity and performance. For dynamic or conditional classes, use Vue bindings (e.g., :class) to compose classes.

Applies to all Vue files in the repository (e.g., src/**/*.vue) where Tailwind utilities are used for static styling.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-16T22:26:49.463Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7537
File: src/components/ui/button/Button.vue:17-17
Timestamp: 2025-12-16T22:26:49.463Z
Learning: In Vue 3.5+ with <script setup>, when using defineProps<Props>() with partial destructuring (e.g., const { as = 'button', class: customClass = '' } = defineProps<Props>() ), props that are not destructured (e.g., variant, size) stay accessible by name in the template scope. This pattern is valid: you can destructure only a subset of props for convenience while referencing the remaining props directly in template expressions. Apply this guideline to Vue components across the codebase (all .vue files).

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-22T21:36:08.369Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7649
File: src/platform/cloud/subscription/components/PricingTable.vue:185-201
Timestamp: 2025-12-22T21:36:08.369Z
Learning: In Vue components, avoid creating single-use variants for common UI components (e.g., Button and other shared components). Aim for reusable variants that cover multiple use cases. It’s acceptable to temporarily mix variant props with inline Tailwind classes when a styling need is unique to one place, but plan and consolidate into shared, reusable variants as patterns emerge across the codebase.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-11T03:55:51.755Z
Learnt from: simula-r
Repo: Comfy-Org/ComfyUI_frontend PR: 7252
File: src/renderer/extensions/vueNodes/components/ImagePreview.vue:151-158
Timestamp: 2025-12-11T03:55:51.755Z
Learning: In Vue components under src/renderer/extensions/vueNodes (e.g., ImagePreview.vue and LGraphNode.vue), implement image gallery keyboard navigation so that it responds to the node's focus state rather than requiring focus inside the image preview wrapper. Achieve this by wiring keyEvent handling at the node focus level and injecting or propagating key events (arrow keys) to the gallery when the node is focused/selected. This improves accessibility and aligns navigation with node-level focus behavior.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-11T12:25:15.470Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7358
File: src/components/dialog/content/signin/SignUpForm.vue:45-54
Timestamp: 2025-12-11T12:25:15.470Z
Learning: This repository uses CI automation to format code (pnpm format). Do not include manual formatting suggestions in code reviews for Comfy-Org/ComfyUI_frontend. If formatting issues are detected, rely on the CI formatter or re-run pnpm format. Focus reviews on correctness, readability, performance, accessibility, and maintainability rather than style formatting.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-18T02:07:38.870Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7598
File: src/components/sidebar/tabs/AssetsSidebarTab.vue:131-131
Timestamp: 2025-12-18T02:07:38.870Z
Learning: Tailwind CSS v4 safe utilities (e.g., items-center-safe, justify-*-safe, place-*-safe) are allowed in Vue components under src/ and in story files. Do not flag these specific safe variants as invalid when reviewing code in src/**/*.vue or related stories.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-18T21:15:46.862Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7603
File: src/components/queue/QueueOverlayHeader.vue:49-59
Timestamp: 2025-12-18T21:15:46.862Z
Learning: In the ComfyUI_frontend repository, for Vue components, do not add aria-label to buttons that have visible text content (e.g., buttons containing <span> text). The visible text provides the accessible name. Use aria-label only for elements without visible labels (e.g., icon-only buttons). If a button has no visible label, provide a clear aria-label or associate with an aria-labelledby describing its action.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-21T01:06:02.786Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7649
File: src/components/graph/selectionToolbox/ColorPickerButton.vue:15-18
Timestamp: 2025-12-21T01:06:02.786Z
Learning: In Comfy-Org/ComfyUI_frontend, in Vue component files, when a filled icon is required (e.g., 'pi pi-circle-fill'), you may mix PrimeIcons with Lucide icons since Lucide lacks filled variants. This mixed usage is acceptable when one icon library does not provide an equivalent filled icon. Apply consistently across Vue components in the src directory where icons are used, and document the rationale when a mixed approach is chosen.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue OverlayPanel component with Popover

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue TabMenu component with Tabs without panels

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-21T06:04:12.562Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T06:04:12.562Z
Learning: Applies to **/*.vue : Do not use the `dark:` Tailwind variant; use semantic values from the `style.css` theme instead (e.g., `bg-node-component-surface`)

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.{vue,css} : Use the correct tokens from style.css in the design system package

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
📚 Learning: 2025-12-21T06:04:12.562Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T06:04:12.562Z
Learning: Applies to **/*.vue : Use Tailwind 4 for styling in Vue components; avoid `<style>` blocks

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
🔇 Additional comments (3)
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue (3)

35-35: LGTM! New prop enables subfolder-aware uploads.

The addition of the optional uploadSubfolder prop is well-integrated with the existing uploadFolder prop and correctly supports the 3D model upload use case.


257-263: LGTM! Subfolder handling is correctly implemented.

The upload function properly prioritizes pasted subfolder over the provided subfolder, and the conditional logic is clear and type-safe.


288-293: LGTM! Subfolder correctly propagated through upload pipeline.

The uploadSubfolder prop is properly extracted and passed to uploadFile, maintaining the upload flow's integrity.

Comment on lines 210 to 211
case 'model':
return '.obj,.stl,.ply,.spz'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for 3D model file extension validation or processing in backend
rg -n '\.(obj|stl|ply|spz|fbx|dae|gltf|glb)' --type-list | head -20

# Search for upload endpoint implementations
rg -n 'upload' --type-list | grep -E '(route|endpoint|def.*upload)' | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 849


🏁 Script executed:

# Search for Python files related to uploads
fd -e py -type f -path '*upload*' 

# Search for file extension validation patterns
rg -n 'accept|extension|format' -t py -A3 -B3 | head -50

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 240


🏁 Script executed:

# Look for backend model configuration or constants
rg -n 'obj|stl|ply|spz' -t py -A2 -B2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1021


🏁 Script executed:

# Check the current file to understand the context better
find . -name 'WidgetSelectDropdown.vue' -exec cat -n {} \;

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 13058


🏁 Script executed:

#!/bin/bash
# Search for upload endpoint handlers in backend
find . -type f -name "*.py" | xargs grep -l "upload" | head -10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 102


🏁 Script executed:

#!/bin/bash
# Search for .spz format references
rg -n "spz" -i

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 50384


🏁 Script executed:

#!/bin/bash
# Search for useNodeImageUpload to understand where this was copied from
fd -e ts -e vue | xargs grep -l "useNodeImageUpload" 2>/dev/null

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 266


🏁 Script executed:

#!/bin/bash
# Search for model format constants or configurations
rg -n "obj|stl|ply" -A2 -B2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 50386


🏁 Script executed:

#!/bin/bash
# Check if there are any other references to acceptTypes or model uploads
rg -n "acceptTypes|assetKind.*model" -A3 -B3

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1634


Update the accept types for 3D model uploads to include all supported formats.

The widget currently restricts 3D model uploads to .obj,.stl,.ply,.spz, but src/extensions/core/load3d.ts reveals the backend supports a broader set of formats: .gltf,.glb,.obj,.fbx,.stl,.ply,.spz,.splat,.ksplat. The comment at lines 201-202 indicates the backend uses libraries "that can handle a wide range of formats," yet the frontend restricts them unnecessarily.

Update line 211 to include at least the commonly used formats:

Suggested update
case 'model':
  return '.obj,.stl,.ply,.spz,.fbx,.gltf,.glb'

Or include the full set for consistency with backend capabilities:

case 'model':
  return '.obj,.stl,.ply,.spz,.fbx,.gltf,.glb,.splat,.ksplat'
🤖 Prompt for AI Agents
In src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
around lines 210-211, the accept list for 'model' currently limits uploads to
.obj,.stl,.ply,.spz but the backend supports more formats; update the returned
string to include the additional supported types (at minimum .fbx,.gltf,.glb, or
include the full set .splat and .ksplat) so the frontend accept attribute
matches backend capabilities; modify the case 'model' return value accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7893c1 and af8fdc4.

📒 Files selected for processing (1)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
🧰 Additional context used
📓 Path-based instructions (9)
src/**/*.vue

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.vue: Use the Vue 3 Composition API instead of the Options API when writing Vue components (exception: when overriding or extending PrimeVue components for compatibility)
Use setup() function for component logic
Utilize ref and reactive for reactive state
Implement computed properties with computed()
Use watch and watchEffect for side effects
Implement lifecycle hooks with onMounted, onUpdated, etc.
Utilize provide/inject for dependency injection
Use vue 3.5 style of default prop declaration
Use Tailwind CSS for styling
Implement proper props and emits definitions
Utilize Vue 3's Teleport component when needed
Use Suspense for async components
Follow Vue 3 style guide and naming conventions

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
src/**/*.{vue,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.{vue,ts}: Leverage VueUse functions for performance-enhancing styles
Implement proper error handling
Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/*.{ts,tsx,vue}: Sanitize HTML with DOMPurify to prevent XSS attacks
Avoid using @ts-expect-error; use proper TypeScript types instead
Use es-toolkit for utility functions instead of other utility libraries
Implement proper TypeScript types throughout the codebase

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
src/**/{composables,components}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Clean up subscriptions in state management to prevent memory leaks

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
src/**/*.{vue,ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
src/**/{components,composables}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Use vue-i18n for ALL user-facing strings by adding them to src/locales/en/main.json

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

**/*.vue: Use Vue 3.5+ with TypeScript in .vue files, exclusively using Composition API with <script setup lang="ts"> syntax
Use Tailwind 4 for styling in Vue components; avoid <style> blocks
Name Vue components using PascalCase (e.g., MenuHamburger.vue)
Use Vue 3.5 TypeScript-style default prop declaration with reactive props destructuring; do not use withDefaults or runtime props declaration
Prefer computed() over ref with watch when deriving values
Prefer useModel over separately defining prop and emit for two-way binding
Use vue-i18n in composition API for string literals; place new translation entries in src/locales/en/main.json
Use cn() utility function from @/utils/tailwindUtil for merging Tailwind class names; do not use :class="[]" syntax
Do not use the dark: Tailwind variant; use semantic values from the style.css theme instead (e.g., bg-node-component-surface)
Do not use !important or the ! important prefix for Tailwind classes; find and correct interfering !important classes instead
Avoid new usage of PrimeVue components; use VueUse, shadcn/vue, or Reka UI instead
Leverage VueUse functions for performance-enhancing styles in Vue components
Implement proper props and emits definitions in Vue components
Utilize Vue 3's Teleport component when needed
Use Suspense for async components
Follow Vue 3 style guide and naming conventions

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,vue}: Use TypeScript exclusively; do not write new JavaScript code
Use sorted and grouped imports organized by plugin/source
Enforce ESLint rules including Vue + TypeScript rules, disallow floating promises, disallow unused imports, and restrict i18n raw text in templates
Do not use any type or as any type assertions; fix the underlying type issue instead
Write code that is expressive and self-documenting; avoid redundant comments and clean as you go
Keep functions short and functional; minimize nesting and follow the arrow anti-pattern
Avoid mutable state; prefer immutability and assignment at point of declaration
Use function declarations instead of function expressions when possible
Use es-toolkit for utility functions
Implement proper error handling in code

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
**/*.{ts,tsx,vue,js,jsx,json,css}

📄 CodeRabbit inference engine (AGENTS.md)

Apply Prettier formatting with 2-space indentation, single quotes, no trailing semicolons, and 80-character line width

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
🧠 Learnings (9)
📚 Learning: 2025-12-09T03:49:52.828Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 6300
File: src/platform/updates/components/WhatsNewPopup.vue:5-13
Timestamp: 2025-12-09T03:49:52.828Z
Learning: In Vue files across the ComfyUI_frontend repo, when a button is needed, prefer the repo's common button components from src/components/button/ (IconButton.vue, TextButton.vue, IconTextButton.vue) over plain HTML <button> elements. These components wrap PrimeVue with the project’s design system styling. Use only the common button components for consistency and theming, and import them from src/components/button/ as needed.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
📚 Learning: 2025-12-09T21:40:12.361Z
Learnt from: benceruleanlu
Repo: Comfy-Org/ComfyUI_frontend PR: 7297
File: src/components/actionbar/ComfyActionbar.vue:33-43
Timestamp: 2025-12-09T21:40:12.361Z
Learning: In Vue single-file components, allow inline Tailwind CSS class strings for static classes and avoid extracting them into computed properties solely for readability. Prefer keeping static class names inline for simplicity and performance. For dynamic or conditional classes, use Vue bindings (e.g., :class) to compose classes.

Applies to all Vue files in the repository (e.g., src/**/*.vue) where Tailwind utilities are used for static styling.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
📚 Learning: 2025-12-16T22:26:49.463Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7537
File: src/components/ui/button/Button.vue:17-17
Timestamp: 2025-12-16T22:26:49.463Z
Learning: In Vue 3.5+ with <script setup>, when using defineProps<Props>() with partial destructuring (e.g., const { as = 'button', class: customClass = '' } = defineProps<Props>() ), props that are not destructured (e.g., variant, size) stay accessible by name in the template scope. This pattern is valid: you can destructure only a subset of props for convenience while referencing the remaining props directly in template expressions. Apply this guideline to Vue components across the codebase (all .vue files).

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
📚 Learning: 2025-12-22T21:36:08.369Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7649
File: src/platform/cloud/subscription/components/PricingTable.vue:185-201
Timestamp: 2025-12-22T21:36:08.369Z
Learning: In Vue components, avoid creating single-use variants for common UI components (e.g., Button and other shared components). Aim for reusable variants that cover multiple use cases. It’s acceptable to temporarily mix variant props with inline Tailwind classes when a styling need is unique to one place, but plan and consolidate into shared, reusable variants as patterns emerge across the codebase.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
📚 Learning: 2025-12-11T03:55:51.755Z
Learnt from: simula-r
Repo: Comfy-Org/ComfyUI_frontend PR: 7252
File: src/renderer/extensions/vueNodes/components/ImagePreview.vue:151-158
Timestamp: 2025-12-11T03:55:51.755Z
Learning: In Vue components under src/renderer/extensions/vueNodes (e.g., ImagePreview.vue and LGraphNode.vue), implement image gallery keyboard navigation so that it responds to the node's focus state rather than requiring focus inside the image preview wrapper. Achieve this by wiring keyEvent handling at the node focus level and injecting or propagating key events (arrow keys) to the gallery when the node is focused/selected. This improves accessibility and aligns navigation with node-level focus behavior.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
📚 Learning: 2025-12-11T12:25:15.470Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7358
File: src/components/dialog/content/signin/SignUpForm.vue:45-54
Timestamp: 2025-12-11T12:25:15.470Z
Learning: This repository uses CI automation to format code (pnpm format). Do not include manual formatting suggestions in code reviews for Comfy-Org/ComfyUI_frontend. If formatting issues are detected, rely on the CI formatter or re-run pnpm format. Focus reviews on correctness, readability, performance, accessibility, and maintainability rather than style formatting.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
📚 Learning: 2025-12-18T02:07:38.870Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7598
File: src/components/sidebar/tabs/AssetsSidebarTab.vue:131-131
Timestamp: 2025-12-18T02:07:38.870Z
Learning: Tailwind CSS v4 safe utilities (e.g., items-center-safe, justify-*-safe, place-*-safe) are allowed in Vue components under src/ and in story files. Do not flag these specific safe variants as invalid when reviewing code in src/**/*.vue or related stories.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
📚 Learning: 2025-12-18T21:15:46.862Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7603
File: src/components/queue/QueueOverlayHeader.vue:49-59
Timestamp: 2025-12-18T21:15:46.862Z
Learning: In the ComfyUI_frontend repository, for Vue components, do not add aria-label to buttons that have visible text content (e.g., buttons containing <span> text). The visible text provides the accessible name. Use aria-label only for elements without visible labels (e.g., icon-only buttons). If a button has no visible label, provide a clear aria-label or associate with an aria-labelledby describing its action.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
📚 Learning: 2025-12-21T01:06:02.786Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7649
File: src/components/graph/selectionToolbox/ColorPickerButton.vue:15-18
Timestamp: 2025-12-21T01:06:02.786Z
Learning: In Comfy-Org/ComfyUI_frontend, in Vue component files, when a filled icon is required (e.g., 'pi pi-circle-fill'), you may mix PrimeIcons with Lucide icons since Lucide lacks filled variants. This mixed usage is acceptable when one icon library does not provide an equivalent filled icon. Apply consistently across Vue components in the src directory where icons are used, and document the rationale when a mixed approach is chosen.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
🔇 Additional comments (5)
src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue (5)

1-13: LGTM!

The new upload-subfolder prop is correctly wired to the WidgetSelectDropdown component, following Vue's kebab-case convention for template prop bindings.


76-82: LGTM!

The fallback || {} for destructuring safely handles the case where spec is undefined (when isLoad3DModel is true but no comboSpec exists), preventing runtime errors while keeping the code concise.


97-112: LGTM!

The allowUpload extension and folder/subfolder logic correctly enable 3D model uploads to the input/3d directory. The implementation is consistent with the existing patterns for other asset types.


137-137: LGTM!

The uploadSubfolder computed property follows the established pattern of the other descriptor-derived computed properties, maintaining consistency in the codebase.


84-93: Code is correct. The 'model' value is properly defined in the AssetKind type, so no TypeScript compilation errors will occur.

Comment on lines 64 to 68
const isLoad3DModel =
props.nodeType === 'Load3D' && props.widget.name === 'model_file'
const spec = comboSpec.value
if (!spec) {
if (!spec && !isLoad3DModel) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider extracting magic strings to constants.

The hardcoded 'Load3D' and 'model_file' strings work correctly but could become maintenance concerns if these identifiers change or are referenced elsewhere. Consider extracting them to named constants for better maintainability and searchability.

🔎 Suggested improvement
+const LOAD_3D_NODE_TYPE = 'Load3D'
+const MODEL_FILE_WIDGET_NAME = 'model_file'
+
 const specDescriptor = computed<{
   kind: AssetKind
   allowUpload: boolean
   folder: ResultItemType | undefined
   subfolder?: string
 }>(() => {
   const isLoad3DModel =
-    props.nodeType === 'Load3D' && props.widget.name === 'model_file'
+    props.nodeType === LOAD_3D_NODE_TYPE && props.widget.name === MODEL_FILE_WIDGET_NAME

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue around
lines 64 to 68, the literal strings 'Load3D' and 'model_file' are used directly;
extract these magic strings into clearly named constants (e.g.,
LOAD_3D_NODE_TYPE and MODEL_FILE_WIDGET_NAME) at the top of the module or in a
shared constants file, replace the inline literals with those constants, and
update any other occurrences in the file to use the constants for consistency
and easier maintenance.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue (2)

210-211: Accept types still incomplete for 3D models.

The past review comment flagging that the backend supports additional formats (.gltf,.glb,.fbx,.splat,.ksplat) has not been addressed. This restricts users from uploading supported file types.


312-328: Type guard and O(n²) complexity issues remain unaddressed.

The past review comment regarding type safety (casting without validation) and O(n²) complexity from reverse().forEach() with splice and unshift has not been resolved.

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af8fdc4 and 328ed07.

📒 Files selected for processing (1)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
🧰 Additional context used
📓 Path-based instructions (9)
src/**/*.vue

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.vue: Use the Vue 3 Composition API instead of the Options API when writing Vue components (exception: when overriding or extending PrimeVue components for compatibility)
Use setup() function for component logic
Utilize ref and reactive for reactive state
Implement computed properties with computed()
Use watch and watchEffect for side effects
Implement lifecycle hooks with onMounted, onUpdated, etc.
Utilize provide/inject for dependency injection
Use vue 3.5 style of default prop declaration
Use Tailwind CSS for styling
Implement proper props and emits definitions
Utilize Vue 3's Teleport component when needed
Use Suspense for async components
Follow Vue 3 style guide and naming conventions

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
src/**/*.{vue,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.{vue,ts}: Leverage VueUse functions for performance-enhancing styles
Implement proper error handling
Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/*.{ts,tsx,vue}: Sanitize HTML with DOMPurify to prevent XSS attacks
Avoid using @ts-expect-error; use proper TypeScript types instead
Use es-toolkit for utility functions instead of other utility libraries
Implement proper TypeScript types throughout the codebase

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
src/**/{composables,components}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Clean up subscriptions in state management to prevent memory leaks

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
src/**/*.{vue,ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
src/**/{components,composables}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Use vue-i18n for ALL user-facing strings by adding them to src/locales/en/main.json

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

**/*.vue: Use Vue 3.5+ with TypeScript in .vue files, exclusively using Composition API with <script setup lang="ts"> syntax
Use Tailwind 4 for styling in Vue components; avoid <style> blocks
Name Vue components using PascalCase (e.g., MenuHamburger.vue)
Use Vue 3.5 TypeScript-style default prop declaration with reactive props destructuring; do not use withDefaults or runtime props declaration
Prefer computed() over ref with watch when deriving values
Prefer useModel over separately defining prop and emit for two-way binding
Use vue-i18n in composition API for string literals; place new translation entries in src/locales/en/main.json
Use cn() utility function from @/utils/tailwindUtil for merging Tailwind class names; do not use :class="[]" syntax
Do not use the dark: Tailwind variant; use semantic values from the style.css theme instead (e.g., bg-node-component-surface)
Do not use !important or the ! important prefix for Tailwind classes; find and correct interfering !important classes instead
Avoid new usage of PrimeVue components; use VueUse, shadcn/vue, or Reka UI instead
Leverage VueUse functions for performance-enhancing styles in Vue components
Implement proper props and emits definitions in Vue components
Utilize Vue 3's Teleport component when needed
Use Suspense for async components
Follow Vue 3 style guide and naming conventions

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,vue}: Use TypeScript exclusively; do not write new JavaScript code
Use sorted and grouped imports organized by plugin/source
Enforce ESLint rules including Vue + TypeScript rules, disallow floating promises, disallow unused imports, and restrict i18n raw text in templates
Do not use any type or as any type assertions; fix the underlying type issue instead
Write code that is expressive and self-documenting; avoid redundant comments and clean as you go
Keep functions short and functional; minimize nesting and follow the arrow anti-pattern
Avoid mutable state; prefer immutability and assignment at point of declaration
Use function declarations instead of function expressions when possible
Use es-toolkit for utility functions
Implement proper error handling in code

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
**/*.{ts,tsx,vue,js,jsx,json,css}

📄 CodeRabbit inference engine (AGENTS.md)

Apply Prettier formatting with 2-space indentation, single quotes, no trailing semicolons, and 80-character line width

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
🧠 Learnings (11)
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue Dropdown component with Select

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-11-24T19:47:34.324Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:34.324Z
Learning: Applies to src/**/*.{ts,tsx,vue} : Implement proper TypeScript types throughout the codebase

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-09T03:49:52.828Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 6300
File: src/platform/updates/components/WhatsNewPopup.vue:5-13
Timestamp: 2025-12-09T03:49:52.828Z
Learning: In Vue files across the ComfyUI_frontend repo, when a button is needed, prefer the repo's common button components from src/components/button/ (IconButton.vue, TextButton.vue, IconTextButton.vue) over plain HTML <button> elements. These components wrap PrimeVue with the project’s design system styling. Use only the common button components for consistency and theming, and import them from src/components/button/ as needed.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-09T21:40:12.361Z
Learnt from: benceruleanlu
Repo: Comfy-Org/ComfyUI_frontend PR: 7297
File: src/components/actionbar/ComfyActionbar.vue:33-43
Timestamp: 2025-12-09T21:40:12.361Z
Learning: In Vue single-file components, allow inline Tailwind CSS class strings for static classes and avoid extracting them into computed properties solely for readability. Prefer keeping static class names inline for simplicity and performance. For dynamic or conditional classes, use Vue bindings (e.g., :class) to compose classes.

Applies to all Vue files in the repository (e.g., src/**/*.vue) where Tailwind utilities are used for static styling.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-16T22:26:49.463Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7537
File: src/components/ui/button/Button.vue:17-17
Timestamp: 2025-12-16T22:26:49.463Z
Learning: In Vue 3.5+ with <script setup>, when using defineProps<Props>() with partial destructuring (e.g., const { as = 'button', class: customClass = '' } = defineProps<Props>() ), props that are not destructured (e.g., variant, size) stay accessible by name in the template scope. This pattern is valid: you can destructure only a subset of props for convenience while referencing the remaining props directly in template expressions. Apply this guideline to Vue components across the codebase (all .vue files).

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-22T21:36:08.369Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7649
File: src/platform/cloud/subscription/components/PricingTable.vue:185-201
Timestamp: 2025-12-22T21:36:08.369Z
Learning: In Vue components, avoid creating single-use variants for common UI components (e.g., Button and other shared components). Aim for reusable variants that cover multiple use cases. It’s acceptable to temporarily mix variant props with inline Tailwind classes when a styling need is unique to one place, but plan and consolidate into shared, reusable variants as patterns emerge across the codebase.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-11T03:55:51.755Z
Learnt from: simula-r
Repo: Comfy-Org/ComfyUI_frontend PR: 7252
File: src/renderer/extensions/vueNodes/components/ImagePreview.vue:151-158
Timestamp: 2025-12-11T03:55:51.755Z
Learning: In Vue components under src/renderer/extensions/vueNodes (e.g., ImagePreview.vue and LGraphNode.vue), implement image gallery keyboard navigation so that it responds to the node's focus state rather than requiring focus inside the image preview wrapper. Achieve this by wiring keyEvent handling at the node focus level and injecting or propagating key events (arrow keys) to the gallery when the node is focused/selected. This improves accessibility and aligns navigation with node-level focus behavior.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-11T12:25:15.470Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7358
File: src/components/dialog/content/signin/SignUpForm.vue:45-54
Timestamp: 2025-12-11T12:25:15.470Z
Learning: This repository uses CI automation to format code (pnpm format). Do not include manual formatting suggestions in code reviews for Comfy-Org/ComfyUI_frontend. If formatting issues are detected, rely on the CI formatter or re-run pnpm format. Focus reviews on correctness, readability, performance, accessibility, and maintainability rather than style formatting.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-18T02:07:38.870Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7598
File: src/components/sidebar/tabs/AssetsSidebarTab.vue:131-131
Timestamp: 2025-12-18T02:07:38.870Z
Learning: Tailwind CSS v4 safe utilities (e.g., items-center-safe, justify-*-safe, place-*-safe) are allowed in Vue components under src/ and in story files. Do not flag these specific safe variants as invalid when reviewing code in src/**/*.vue or related stories.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-18T21:15:46.862Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7603
File: src/components/queue/QueueOverlayHeader.vue:49-59
Timestamp: 2025-12-18T21:15:46.862Z
Learning: In the ComfyUI_frontend repository, for Vue components, do not add aria-label to buttons that have visible text content (e.g., buttons containing <span> text). The visible text provides the accessible name. Use aria-label only for elements without visible labels (e.g., icon-only buttons). If a button has no visible label, provide a clear aria-label or associate with an aria-labelledby describing its action.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-21T01:06:02.786Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7649
File: src/components/graph/selectionToolbox/ColorPickerButton.vue:15-18
Timestamp: 2025-12-21T01:06:02.786Z
Learning: In Comfy-Org/ComfyUI_frontend, in Vue component files, when a filled icon is required (e.g., 'pi pi-circle-fill'), you may mix PrimeIcons with Lucide icons since Lucide lacks filled variants. This mixed usage is acceptable when one icon library does not provide an equivalent filled icon. Apply consistently across Vue components in the src directory where icons are used, and document the rationale when a mixed approach is chosen.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
🔇 Additional comments (3)
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue (3)

35-35: LGTM!

The new uploadSubfolder prop follows the Vue 3.5 TypeScript-style prop declaration pattern and integrates cleanly with the existing props interface.


257-263: LGTM!

The subfolder handling correctly prioritizes the isPasted condition over the formFields.subfolder, and the FormData append logic is sound.


288-296: LGTM!

The subfolder prop is correctly propagated through to the uploadFile function.

Comment on lines +316 to 323
uploadedPaths.reverse().forEach((path) => {
// Remove existing duplicates to move them to top
const existingIndex = values.indexOf(path)
if (existingIndex > -1) {
values.splice(existingIndex, 1)
}
values.unshift(path)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Logic bug: modelValue and dropdown order are inconsistent for multi-file uploads.

The reverse() + unshift() pattern results in the first uploaded file at values[0], but modelValue is set to uploadedPaths[0] which, after the in-place reverse, is the last uploaded file. This creates a confusing UX where the selected value doesn't match the top dropdown item.

Trace for uploadedPaths = ['a.obj', 'b.obj', 'c.obj']:

  • After reverse(): ['c.obj', 'b.obj', 'a.obj']
  • After forEach with unshift: values = ['a.obj', 'b.obj', 'c.obj', ...existing]
  • modelValue = uploadedPaths[0] = 'c.obj'

Result: 'a.obj' is at top of dropdown, but 'c.obj' is selected.

🔎 Proposed fix to align modelValue with values[0]
     if (props.widget.options?.values) {
       const values = props.widget.options.values as string[]

-      // Reverse uploadedPaths so the very last uploaded is at absolute top if multiple
-      uploadedPaths.reverse().forEach((path) => {
+      // Add uploaded paths to top, maintaining upload order (first uploaded at top)
+      ;[...uploadedPaths].reverse().forEach((path) => {
         // Remove existing duplicates to move them to top
         const existingIndex = values.indexOf(path)
         if (existingIndex > -1) {
           values.splice(existingIndex, 1)
         }
         values.unshift(path)
       })

       // Enforce limit of 12
       if (values.length > 12) {
         values.splice(12)
       }
     }
     // 3. Update widget value to the first uploaded file
-    modelValue.value = uploadedPaths[0]
+    modelValue.value = uploadedPaths[0] // Now correctly matches values[0]

By spreading into a new array before reversing, the original uploadedPaths order is preserved for modelValue.

Also applies to: 331-331

🤖 Prompt for AI Agents
In src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
around lines 316-323 (and similarly at line 331), the code uses
uploadedPaths.reverse() which mutates the original array and causes modelValue
to reference the wrong element; instead, create a non-mutating reversed copy
(e.g. const toInsert = [...uploadedPaths].reverse()), iterate to remove
duplicates and unshift from toInsert so values is built correctly, then set the
modelValue to values[0] (or derive modelValue from the non-mutated ordering) so
the selected item matches the top dropdown entry; apply the same non-mutating
approach at the other occurrence on line 331.

@kaili-yang
Copy link
Contributor Author

Closing temporarily — issues identified and improvements needed. Will revisit.

@kaili-yang kaili-yang closed this Jan 5, 2026
@kaili-yang kaili-yang reopened this Jan 6, 2026
@kaili-yang kaili-yang requested a review from jtydhr88 as a code owner January 6, 2026 03:38
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Jan 6, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Fix all issues with AI Agents
In @src/extensions/core/load3d/Load3DConfiguration.ts:
- Line 47: Extract the magic values by adding named constants (e.g.,
HIDDEN_FILES_STORAGE_KEY and MAX_MESH_OPTIONS) at the class or module level and
replace the inline string 'Comfy.Load3D.HiddenFiles' and the numeric literal 12
with those constants; update usages in Load3DConfiguration.configure (and any
other occurrences in this file) to reference
Load3DConfiguration.HIDDEN_FILES_STORAGE_KEY and
Load3DConfiguration.MAX_MESH_OPTIONS (or module-scoped equivalents) for
maintainability and consistency.
- Around line 43-61: The code casts setting.meshWidget.options.values to
string[] without verifying its shape; add a type guard to ensure it's an array
of strings before using it (e.g., check
Array.isArray(setting.meshWidget.options.values) and filter elements with typeof
v === 'string'), fallback to an empty array if the guard fails, then continue
using the validated string[] for the localStorage filtering/slicing and reassign
back to setting.meshWidget.options.values.
- Around line 43-61: The code mutates the local variable values via
reassignment; change to an immutable flow: keep the original array (e.g.,
originalValues = setting.meshWidget.options.values as string[]), then compute a
new array by applying filter based on parsed hiddenFiles and then applying
slice(0, 12) as needed, and finally assign that new array to
setting.meshWidget.options.values; reference the symbol
setting.meshWidget.options.values and the localStorage key
'Comfy.Load3D.HiddenFiles' when locating the logic to refactor.
- Line 237: The awaited call to this.load3d.loadModel(meshUrl, filename) can
throw and is currently unhandled; wrap the call in a try/catch inside the
callback handler where loadModel is invoked, catch any errors from
this.load3d.loadModel(meshUrl, filename), log or report the error (including
meshUrl and filename) via the existing logger or error handler, and then either
return/propagate an appropriate error result or rethrow after logging so callers
don’t get an unhandled rejection.

In @src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue:
- Around line 64-65: Extract the magic strings used in the isLoad3DMesh
expression into named constants (e.g., LOAD3D_NODE_TYPE and MODEL_FILE_WIDGET)
and replace the inline literals in the check (currently using props.nodeType ===
'Load3D' and props.widget.name === 'model_file') with those constants; define
the constants near the top of WidgetSelect.vue (or in a shared constants module
if used elsewhere) and update any other occurrences to use the new constant
names to improve maintainability and searchability.

In
@src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue:
- Around line 211-212: The accept list for the 'mesh' option is missing several
supported 3D formats; update the returned string in the WidgetSelectDropdown.vue
branch that handles case 'mesh' (the return '.obj,.stl,.ply,.spz' expression) to
include the additional extensions .gltf, .glb, .fbx, .splat, and .ksplat so the
frontend matches the backend supported formats.
- Around line 314-332: The upload handling mutates uploadedPaths with reverse(),
casts props.widget.options.values to string[] without checking, and does O(n²)
splices; fix by: validate props.widget.options.values and uploadedPaths using
Array.isArray and filter to strings before use (refer to
props.widget.options.values and uploadedPaths), work on non-mutating copies
(e.g., const uploads = [...uploadedPathsFiltered]) and reverse the copy (not
uploadedPaths) so original order is preserved, dedupe using a Set to avoid
repeated indexOf/splice (avoid O(n²)) while building the new values array,
enforce the 12-item cap on the new array, and finally set modelValue.value to
the true first uploaded item from the validated uploads array (e.g., uploads[0])
so it references the first uploaded file.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 328ed07 and f47ddb0.

📒 Files selected for processing (6)
  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/types/widgetTypes.ts
🧰 Additional context used
📓 Path-based instructions (13)
src/**/*.vue

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.vue: Use the Vue 3 Composition API instead of the Options API when writing Vue components (exception: when overriding or extending PrimeVue components for compatibility)
Use setup() function for component logic
Utilize ref and reactive for reactive state
Implement computed properties with computed()
Use watch and watchEffect for side effects
Implement lifecycle hooks with onMounted, onUpdated, etc.
Utilize provide/inject for dependency injection
Use vue 3.5 style of default prop declaration
Use Tailwind CSS for styling
Implement proper props and emits definitions
Utilize Vue 3's Teleport component when needed
Use Suspense for async components
Follow Vue 3 style guide and naming conventions

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
src/**/*.{vue,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.{vue,ts}: Leverage VueUse functions for performance-enhancing styles
Implement proper error handling
Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/types/widgetTypes.ts
  • src/extensions/core/load3d.ts
src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/*.{ts,tsx,vue}: Sanitize HTML with DOMPurify to prevent XSS attacks
Avoid using @ts-expect-error; use proper TypeScript types instead
Use es-toolkit for utility functions instead of other utility libraries
Implement proper TypeScript types throughout the codebase

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/types/widgetTypes.ts
  • src/extensions/core/load3d.ts
src/**/{composables,components}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Clean up subscriptions in state management to prevent memory leaks

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/composables/useLoad3d.ts
src/**/*.{vue,ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/types/widgetTypes.ts
  • src/extensions/core/load3d.ts
src/**/{components,composables}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Use vue-i18n for ALL user-facing strings by adding them to src/locales/en/main.json

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/composables/useLoad3d.ts
**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

**/*.vue: Use Vue 3.5+ with TypeScript in .vue files, exclusively using Composition API with <script setup lang="ts"> syntax
Use Tailwind 4 for styling in Vue components; avoid <style> blocks
Name Vue components using PascalCase (e.g., MenuHamburger.vue)
Use Vue 3.5 TypeScript-style default prop declaration with reactive props destructuring; do not use withDefaults or runtime props declaration
Prefer computed() over ref with watch when deriving values
Prefer useModel over separately defining prop and emit for two-way binding
Use vue-i18n in composition API for string literals; place new translation entries in src/locales/en/main.json
Use cn() utility function from @/utils/tailwindUtil for merging Tailwind class names; do not use :class="[]" syntax
Do not use the dark: Tailwind variant; use semantic values from the style.css theme instead (e.g., bg-node-component-surface)
Do not use !important or the ! important prefix for Tailwind classes; find and correct interfering !important classes instead
Avoid new usage of PrimeVue components; use VueUse, shadcn/vue, or Reka UI instead
Leverage VueUse functions for performance-enhancing styles in Vue components
Implement proper props and emits definitions in Vue components
Utilize Vue 3's Teleport component when needed
Use Suspense for async components
Follow Vue 3 style guide and naming conventions

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,vue}: Use TypeScript exclusively; do not write new JavaScript code
Use sorted and grouped imports organized by plugin/source
Enforce ESLint rules including Vue + TypeScript rules, disallow floating promises, disallow unused imports, and restrict i18n raw text in templates
Do not use any type or as any type assertions; fix the underlying type issue instead
Write code that is expressive and self-documenting; avoid redundant comments and clean as you go
Keep functions short and functional; minimize nesting and follow the arrow anti-pattern
Avoid mutable state; prefer immutability and assignment at point of declaration
Use function declarations instead of function expressions when possible
Use es-toolkit for utility functions
Implement proper error handling in code

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/types/widgetTypes.ts
  • src/extensions/core/load3d.ts
**/*.{ts,tsx,vue,js,jsx,json,css}

📄 CodeRabbit inference engine (AGENTS.md)

Apply Prettier formatting with 2-space indentation, single quotes, no trailing semicolons, and 80-character line width

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/types/widgetTypes.ts
  • src/extensions/core/load3d.ts
src/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

src/**/*.ts: Use es-toolkit for utility functions
Use TypeScript for type safety

Files:

  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/types/widgetTypes.ts
  • src/extensions/core/load3d.ts
src/**/{services,composables}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/{services,composables}/**/*.{ts,tsx}: Use api.apiURL() for backend endpoints instead of constructing URLs directly
Use api.fileURL() for static file access instead of constructing URLs directly

Files:

  • src/composables/useLoad3d.ts
**/**/use[A-Z]*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name composables using the pattern useXyz.ts

Files:

  • src/composables/useLoad3d.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Minimize the surface area (exported values) of each module and composable

Files:

  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/types/widgetTypes.ts
  • src/extensions/core/load3d.ts
🧠 Learnings (17)
📚 Learning: 2025-12-09T03:49:52.828Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 6300
File: src/platform/updates/components/WhatsNewPopup.vue:5-13
Timestamp: 2025-12-09T03:49:52.828Z
Learning: In Vue files across the ComfyUI_frontend repo, when a button is needed, prefer the repo's common button components from src/components/button/ (IconButton.vue, TextButton.vue, IconTextButton.vue) over plain HTML <button> elements. These components wrap PrimeVue with the project’s design system styling. Use only the common button components for consistency and theming, and import them from src/components/button/ as needed.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-09T21:40:12.361Z
Learnt from: benceruleanlu
Repo: Comfy-Org/ComfyUI_frontend PR: 7297
File: src/components/actionbar/ComfyActionbar.vue:33-43
Timestamp: 2025-12-09T21:40:12.361Z
Learning: In Vue single-file components, allow inline Tailwind CSS class strings for static classes and avoid extracting them into computed properties solely for readability. Prefer keeping static class names inline for simplicity and performance. For dynamic or conditional classes, use Vue bindings (e.g., :class) to compose classes.

Applies to all Vue files in the repository (e.g., src/**/*.vue) where Tailwind utilities are used for static styling.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-16T22:26:49.463Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7537
File: src/components/ui/button/Button.vue:17-17
Timestamp: 2025-12-16T22:26:49.463Z
Learning: In Vue 3.5+ with <script setup>, when using defineProps<Props>() with partial destructuring (e.g., const { as = 'button', class: customClass = '' } = defineProps<Props>() ), props that are not destructured (e.g., variant, size) stay accessible by name in the template scope. This pattern is valid: you can destructure only a subset of props for convenience while referencing the remaining props directly in template expressions. Apply this guideline to Vue components across the codebase (all .vue files).

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-22T21:36:08.369Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7649
File: src/platform/cloud/subscription/components/PricingTable.vue:185-201
Timestamp: 2025-12-22T21:36:08.369Z
Learning: In Vue components, avoid creating single-use variants for common UI components (e.g., Button and other shared components). Aim for reusable variants that cover multiple use cases. It’s acceptable to temporarily mix variant props with inline Tailwind classes when a styling need is unique to one place, but plan and consolidate into shared, reusable variants as patterns emerge across the codebase.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-11T03:55:51.755Z
Learnt from: simula-r
Repo: Comfy-Org/ComfyUI_frontend PR: 7252
File: src/renderer/extensions/vueNodes/components/ImagePreview.vue:151-158
Timestamp: 2025-12-11T03:55:51.755Z
Learning: In Vue components under src/renderer/extensions/vueNodes (e.g., ImagePreview.vue and LGraphNode.vue), implement image gallery keyboard navigation so that it responds to the node's focus state rather than requiring focus inside the image preview wrapper. Achieve this by wiring keyEvent handling at the node focus level and injecting or propagating key events (arrow keys) to the gallery when the node is focused/selected. This improves accessibility and aligns navigation with node-level focus behavior.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-11T12:25:15.470Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7358
File: src/components/dialog/content/signin/SignUpForm.vue:45-54
Timestamp: 2025-12-11T12:25:15.470Z
Learning: This repository uses CI automation to format code (pnpm format). Do not include manual formatting suggestions in code reviews for Comfy-Org/ComfyUI_frontend. If formatting issues are detected, rely on the CI formatter or re-run pnpm format. Focus reviews on correctness, readability, performance, accessibility, and maintainability rather than style formatting.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/types/widgetTypes.ts
  • src/extensions/core/load3d.ts
📚 Learning: 2025-12-18T02:07:38.870Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7598
File: src/components/sidebar/tabs/AssetsSidebarTab.vue:131-131
Timestamp: 2025-12-18T02:07:38.870Z
Learning: Tailwind CSS v4 safe utilities (e.g., items-center-safe, justify-*-safe, place-*-safe) are allowed in Vue components under src/ and in story files. Do not flag these specific safe variants as invalid when reviewing code in src/**/*.vue or related stories.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-18T21:15:46.862Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7603
File: src/components/queue/QueueOverlayHeader.vue:49-59
Timestamp: 2025-12-18T21:15:46.862Z
Learning: In the ComfyUI_frontend repository, for Vue components, do not add aria-label to buttons that have visible text content (e.g., buttons containing <span> text). The visible text provides the accessible name. Use aria-label only for elements without visible labels (e.g., icon-only buttons). If a button has no visible label, provide a clear aria-label or associate with an aria-labelledby describing its action.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-12-21T01:06:02.786Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7649
File: src/components/graph/selectionToolbox/ColorPickerButton.vue:15-18
Timestamp: 2025-12-21T01:06:02.786Z
Learning: In Comfy-Org/ComfyUI_frontend, in Vue component files, when a filled icon is required (e.g., 'pi pi-circle-fill'), you may mix PrimeIcons with Lucide icons since Lucide lacks filled variants. This mixed usage is acceptable when one icon library does not provide an equivalent filled icon. Apply consistently across Vue components in the src directory where icons are used, and document the rationale when a mixed approach is chosen.

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue Dropdown component with Select

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-11-24T19:47:34.324Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:34.324Z
Learning: Applies to src/**/*.{ts,tsx,vue} : Implement proper TypeScript types throughout the codebase

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-11-24T19:47:34.324Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:34.324Z
Learning: Applies to src/**/{services,composables}/**/*.{ts,tsx} : Use `api.fileURL()` for static file access instead of constructing URLs directly

Applied to files:

  • src/composables/useLoad3d.ts
📚 Learning: 2025-12-09T03:39:54.501Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7169
File: src/platform/remote/comfyui/jobs/jobTypes.ts:1-107
Timestamp: 2025-12-09T03:39:54.501Z
Learning: In the ComfyUI_frontend project, Zod is on v3.x. Do not suggest Zod v4 standalone validators (z.uuid, z.ulid, z.cuid2, z.nanoid) until an upgrade to Zod 4 is performed. When reviewing TypeScript files (e.g., src/platform/remote/comfyui/jobs/jobTypes.ts) validate against Zod 3 capabilities and avoid introducing v4-specific features; flag any proposal to upgrade or incorporate v4-only validators and propose staying with compatible 3.x patterns.

Applied to files:

  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/types/widgetTypes.ts
  • src/extensions/core/load3d.ts
📚 Learning: 2025-12-13T11:03:11.264Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7416
File: src/stores/imagePreviewStore.ts:5-7
Timestamp: 2025-12-13T11:03:11.264Z
Learning: In the ComfyUI_frontend repository, lint rules require keeping 'import type' statements separate from non-type imports, even if importing from the same module. Do not suggest consolidating them into a single import statement. Ensure type imports remain on their own line (import type { ... } from 'module') and regular imports stay on separate lines.

Applied to files:

  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/types/widgetTypes.ts
  • src/extensions/core/load3d.ts
📚 Learning: 2025-12-17T00:40:09.635Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7537
File: src/components/ui/button/Button.stories.ts:45-55
Timestamp: 2025-12-17T00:40:09.635Z
Learning: Prefer pure function declarations over function expressions (e.g., use function foo() { ... } instead of const foo = () => { ... }) for pure functions in the repository. Function declarations are more functional-leaning, offer better hoisting clarity, and can improve readability and tooling consistency. Apply this guideline across TypeScript files in Comfy-Org/ComfyUI_frontend, including story and UI component code, except where a function expression is semantically required (e.g., callbacks, higher-order functions with closures).

Applied to files:

  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/types/widgetTypes.ts
  • src/extensions/core/load3d.ts
📚 Learning: 2025-12-30T22:22:33.836Z
Learnt from: kaili-yang
Repo: Comfy-Org/ComfyUI_frontend PR: 7805
File: src/composables/useCoreCommands.ts:439-439
Timestamp: 2025-12-30T22:22:33.836Z
Learning: When accessing reactive properties from Pinia stores in TypeScript files, avoid using .value on direct property access (e.g., useStore().isOverlayExpanded). Pinia auto-wraps refs when accessed directly, returning the primitive value. The .value accessor is only needed when destructuring store properties or when using storeToRefs().

Applied to files:

  • src/composables/useLoad3d.ts
  • src/extensions/core/load3d/Load3DConfiguration.ts
  • src/types/widgetTypes.ts
  • src/extensions/core/load3d.ts
📚 Learning: 2025-12-21T06:04:12.562Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-21T06:04:12.562Z
Learning: Applies to **/*.{ts,tsx,vue} : Do not use `any` type or `as any` type assertions; fix the underlying type issue instead

Applied to files:

  • src/extensions/core/load3d/Load3DConfiguration.ts
🧬 Code graph analysis (3)
src/composables/useLoad3d.ts (2)
src/platform/updates/common/toastStore.ts (1)
  • useToastStore (8-39)
src/scripts/api.ts (1)
  • api (1306-1306)
src/extensions/core/load3d/Load3DConfiguration.ts (2)
src/lib/litegraph/src/types/widgets.ts (1)
  • IBaseWidget (271-430)
src/extensions/core/load3d/interfaces.ts (1)
  • CameraState (20-25)
src/extensions/core/load3d.ts (2)
src/scripts/api.ts (1)
  • api (1306-1306)
src/platform/updates/common/toastStore.ts (1)
  • useToastStore (8-39)
🔇 Additional comments (10)
src/types/widgetTypes.ts (1)

3-3: LGTM! Clean terminology update.

The rename from 'model' to 'mesh' is consistent with the broader migration across the codebase and improves clarity for 3D asset handling.

src/composables/useLoad3d.ts (2)

146-167: LGTM! Proper mesh widget callback handling.

The callback wrapping correctly intercepts widget changes to reload the 3D model. Error handling with loading states and toast notifications is appropriate.


230-258: LGTM! Robust URL construction logic.

The getMeshUrl function properly handles HTTP URLs, [output] suffix annotations, and path splitting with appropriate error handling.

src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue (1)

11-11: LGTM! Proper subfolder support for 3D assets.

The new uploadSubfolder prop and its integration with the mesh detection logic correctly enables subfolder-based uploads for 3D models.

Also applies to: 35-35, 104-105, 137-137

src/extensions/core/load3d.ts (2)

38-82: LGTM! Consistent mesh terminology and improved error handling.

The renaming from modelWidget to meshWidget is consistent throughout handleModelUpload. The addition of a toast alert on upload failure (line 52) improves user feedback.


288-291: LGTM! Consistent mesh widget references across node lifecycle.

The mesh widget handling is properly implemented in both Load3D and Preview3D node creation and configuration flows.

Also applies to: 338-351, 467-484, 501-512

src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue (2)

35-35: LGTM! Subfolder support properly integrated.

The uploadSubfolder prop is correctly threaded through the upload flow to enable subfolder-based file uploads for 3D meshes.

Also applies to: 258-264, 291-293


118-128: LGTM! Mesh asset support properly integrated.

The mesh asset kind is correctly handled in output items detection and placeholder mapping, consistent with the broader asset handling pattern.

Also applies to: 187-188

src/extensions/core/load3d/Load3DConfiguration.ts (2)

16-23: LGTM: Type definition updated consistently.

The rename from modelWidget to meshWidget aligns with the PR's terminology shift and is applied consistently throughout the file.


31-34: LGTM: Consistent terminology refactoring from model to mesh.

The refactoring successfully updates all references from "model" to "mesh" terminology throughout the configuration class. Function names, parameters, variables, and method calls are all updated consistently without introducing logic changes.

Also applies to: 37-41, 81-126, 218-237

Comment on lines +43 to +61
if (setting.meshWidget.options?.values) {
let values = setting.meshWidget.options.values as string[]

try {
const stored = localStorage.getItem('Comfy.Load3D.HiddenFiles')
const hiddenFiles = stored ? JSON.parse(stored) : []
if (hiddenFiles.length > 0) {
values = values.filter((v) => !hiddenFiles.includes(v))
}
} catch (e) {
console.error('Failed to read hidden files from localStorage', e)
}

if (values.length > 12) {
values = values.slice(0, 12)
}

setting.meshWidget.options.values = values
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add type guard before casting to string[].

The code casts setting.meshWidget.options.values to string[] without verifying that it's actually an array of strings. This could cause runtime errors if the values contain non-string elements.

🔎 Proposed fix with type guard
 if (setting.meshWidget.options?.values) {
-  let values = setting.meshWidget.options.values as string[]
+  const rawValues = setting.meshWidget.options.values
+  if (!Array.isArray(rawValues)) {
+    return
+  }
+  let values = rawValues.filter((v): v is string => typeof v === 'string')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (setting.meshWidget.options?.values) {
let values = setting.meshWidget.options.values as string[]
try {
const stored = localStorage.getItem('Comfy.Load3D.HiddenFiles')
const hiddenFiles = stored ? JSON.parse(stored) : []
if (hiddenFiles.length > 0) {
values = values.filter((v) => !hiddenFiles.includes(v))
}
} catch (e) {
console.error('Failed to read hidden files from localStorage', e)
}
if (values.length > 12) {
values = values.slice(0, 12)
}
setting.meshWidget.options.values = values
}
if (setting.meshWidget.options?.values) {
const rawValues = setting.meshWidget.options.values
if (!Array.isArray(rawValues)) {
return
}
let values = rawValues.filter((v): v is string => typeof v === 'string')
try {
const stored = localStorage.getItem('Comfy.Load3D.HiddenFiles')
const hiddenFiles = stored ? JSON.parse(stored) : []
if (hiddenFiles.length > 0) {
values = values.filter((v) => !hiddenFiles.includes(v))
}
} catch (e) {
console.error('Failed to read hidden files from localStorage', e)
}
if (values.length > 12) {
values = values.slice(0, 12)
}
setting.meshWidget.options.values = values
}
🤖 Prompt for AI Agents
In @src/extensions/core/load3d/Load3DConfiguration.ts around lines 43-61, The
code casts setting.meshWidget.options.values to string[] without verifying its
shape; add a type guard to ensure it's an array of strings before using it
(e.g., check Array.isArray(setting.meshWidget.options.values) and filter
elements with typeof v === 'string'), fallback to an empty array if the guard
fails, then continue using the validated string[] for the localStorage
filtering/slicing and reassign back to setting.meshWidget.options.values.

🧹 Nitpick | 🔵 Trivial

Consider using immutable operations for filtering and slicing.

The current implementation mutates the values variable through reassignment. While this works, using immutable patterns would be more aligned with functional programming best practices and coding guidelines.

🔎 Proposed refactor using immutability
 if (setting.meshWidget.options?.values) {
-  let values = setting.meshWidget.options.values as string[]
+  const rawValues = setting.meshWidget.options.values
+  if (!Array.isArray(rawValues)) {
+    return
+  }
+  
+  const validValues = rawValues.filter((v): v is string => typeof v === 'string')

   try {
     const stored = localStorage.getItem('Comfy.Load3D.HiddenFiles')
     const hiddenFiles = stored ? JSON.parse(stored) : []
-    if (hiddenFiles.length > 0) {
-      values = values.filter((v) => !hiddenFiles.includes(v))
-    }
+    const filteredValues = hiddenFiles.length > 0
+      ? validValues.filter((v) => !hiddenFiles.includes(v))
+      : validValues
+    
+    const limitedValues = filteredValues.length > 12
+      ? filteredValues.slice(0, 12)
+      : filteredValues
+    
+    setting.meshWidget.options.values = limitedValues
   } catch (e) {
     console.error('Failed to read hidden files from localStorage', e)
+    setting.meshWidget.options.values = validValues.slice(0, 12)
   }
-
-  if (values.length > 12) {
-    values = values.slice(0, 12)
-  }
-
-  setting.meshWidget.options.values = values
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (setting.meshWidget.options?.values) {
let values = setting.meshWidget.options.values as string[]
try {
const stored = localStorage.getItem('Comfy.Load3D.HiddenFiles')
const hiddenFiles = stored ? JSON.parse(stored) : []
if (hiddenFiles.length > 0) {
values = values.filter((v) => !hiddenFiles.includes(v))
}
} catch (e) {
console.error('Failed to read hidden files from localStorage', e)
}
if (values.length > 12) {
values = values.slice(0, 12)
}
setting.meshWidget.options.values = values
}
if (setting.meshWidget.options?.values) {
const rawValues = setting.meshWidget.options.values
if (!Array.isArray(rawValues)) {
return
}
const validValues = rawValues.filter(
(v): v is string => typeof v === 'string'
)
try {
const stored = localStorage.getItem('Comfy.Load3D.HiddenFiles')
const hiddenFiles = stored ? JSON.parse(stored) : []
const filteredValues = hiddenFiles.length > 0
? validValues.filter((v) => !hiddenFiles.includes(v))
: validValues
const limitedValues = filteredValues.length > 12
? filteredValues.slice(0, 12)
: filteredValues
setting.meshWidget.options.values = limitedValues
} catch (e) {
console.error('Failed to read hidden files from localStorage', e)
setting.meshWidget.options.values = validValues.slice(0, 12)
}
}
🤖 Prompt for AI Agents
In @src/extensions/core/load3d/Load3DConfiguration.ts around lines 43-61, The
code mutates the local variable values via reassignment; change to an immutable
flow: keep the original array (e.g., originalValues =
setting.meshWidget.options.values as string[]), then compute a new array by
applying filter based on parsed hiddenFiles and then applying slice(0, 12) as
needed, and finally assign that new array to setting.meshWidget.options.values;
reference the symbol setting.meshWidget.options.values and the localStorage key
'Comfy.Load3D.HiddenFiles' when locating the logic to refactor.

let values = setting.meshWidget.options.values as string[]

try {
const stored = localStorage.getItem('Comfy.Load3D.HiddenFiles')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Extract magic constants for maintainability.

The hard-coded localStorage key 'Comfy.Load3D.HiddenFiles' and the magic number 12 should be extracted as named constants at the class or module level for better maintainability and consistency.

🔎 Proposed refactor

Add constants at the top of the class:

class Load3DConfiguration {
  private static readonly HIDDEN_FILES_STORAGE_KEY = 'Comfy.Load3D.HiddenFiles'
  private static readonly MAX_MESH_OPTIONS = 12

  // ... rest of the class

Then use them in the configure method:

     try {
-      const stored = localStorage.getItem('Comfy.Load3D.HiddenFiles')
+      const stored = localStorage.getItem(Load3DConfiguration.HIDDEN_FILES_STORAGE_KEY)
       const hiddenFiles = stored ? JSON.parse(stored) : []
       if (hiddenFiles.length > 0) {
         values = values.filter((v) => !hiddenFiles.includes(v))
       }
     } catch (e) {
       console.error('Failed to read hidden files from localStorage', e)
     }

-    if (values.length > 12) {
-      values = values.slice(0, 12)
+    if (values.length > Load3DConfiguration.MAX_MESH_OPTIONS) {
+      values = values.slice(0, Load3DConfiguration.MAX_MESH_OPTIONS)
     }

Also applies to: 56-56

🤖 Prompt for AI Agents
In @src/extensions/core/load3d/Load3DConfiguration.ts around line 47, Extract
the magic values by adding named constants (e.g., HIDDEN_FILES_STORAGE_KEY and
MAX_MESH_OPTIONS) at the class or module level and replace the inline string
'Comfy.Load3D.HiddenFiles' and the numeric literal 12 with those constants;
update usages in Load3DConfiguration.configure (and any other occurrences in
this file) to reference Load3DConfiguration.HIDDEN_FILES_STORAGE_KEY and
Load3DConfiguration.MAX_MESH_OPTIONS (or module-scoped equivalents) for
maintainability and consistency.

)

await this.load3d.loadModel(modelUrl, filename)
await this.load3d.loadModel(meshUrl, filename)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for loadModel call.

The loadModel call on line 237 is awaited but not wrapped in error handling. If the model fails to load (e.g., network error, invalid file format, file not found), the error will propagate uncaught. Since this function is used as a callback handler, unhandled errors could lead to silent failures or uncaught promise rejections.

🔎 Proposed fix with error handling
-      await this.load3d.loadModel(meshUrl, filename)
+      try {
+        await this.load3d.loadModel(meshUrl, filename)
+      } catch (error) {
+        console.error('Failed to load 3D mesh:', error)
+        return
+      }
🤖 Prompt for AI Agents
In @src/extensions/core/load3d/Load3DConfiguration.ts around line 237, The
awaited call to this.load3d.loadModel(meshUrl, filename) can throw and is
currently unhandled; wrap the call in a try/catch inside the callback handler
where loadModel is invoked, catch any errors from this.load3d.loadModel(meshUrl,
filename), log or report the error (including meshUrl and filename) via the
existing logger or error handler, and then either return/propagate an
appropriate error result or rethrow after logging so callers don’t get an
unhandled rejection.

Comment on lines +64 to +65
const isLoad3DMesh =
props.nodeType === 'Load3D' && props.widget.name === 'model_file'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider extracting magic strings to constants.

The hardcoded 'Load3D' and 'model_file' strings work correctly but could become maintenance concerns if these identifiers change or are referenced elsewhere. Consider extracting them to named constants for better maintainability and searchability.

🔎 Suggested improvement
+const LOAD_3D_NODE_TYPE = 'Load3D'
+const MODEL_FILE_WIDGET_NAME = 'model_file'
+
 const specDescriptor = computed<{
   kind: AssetKind
   allowUpload: boolean
   folder: ResultItemType | undefined
   subfolder?: string
 }>(() => {
   const isLoad3DMesh =
-    props.nodeType === 'Load3D' && props.widget.name === 'model_file'
+    props.nodeType === LOAD_3D_NODE_TYPE && props.widget.name === MODEL_FILE_WIDGET_NAME
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isLoad3DMesh =
props.nodeType === 'Load3D' && props.widget.name === 'model_file'
const LOAD_3D_NODE_TYPE = 'Load3D'
const MODEL_FILE_WIDGET_NAME = 'model_file'
const specDescriptor = computed<{
kind: AssetKind
allowUpload: boolean
folder: ResultItemType | undefined
subfolder?: string
}>(() => {
const isLoad3DMesh =
props.nodeType === LOAD_3D_NODE_TYPE &&
props.widget.name === MODEL_FILE_WIDGET_NAME
🤖 Prompt for AI Agents
In @src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue around
lines 64-65, Extract the magic strings used in the isLoad3DMesh expression into
named constants (e.g., LOAD3D_NODE_TYPE and MODEL_FILE_WIDGET) and replace the
inline literals in the check (currently using props.nodeType === 'Load3D' and
props.widget.name === 'model_file') with those constants; define the constants
near the top of WidgetSelect.vue (or in a shared constants module if used
elsewhere) and update any other occurrences to use the new constant names to
improve maintainability and searchability.

Comment on lines +211 to +212
case 'mesh':
return '.obj,.stl,.ply,.spz'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update accept types to include all supported 3D formats.

The current accept types for 'mesh' only include .obj,.stl,.ply,.spz, but src/extensions/core/load3d.ts (lines 253-254) shows the backend supports additional formats: .gltf,.glb,.fbx,.splat,.ksplat. Restricting the frontend unnecessarily prevents users from uploading valid formats.

🔎 Proposed fix
     case 'mesh':
-      return '.obj,.stl,.ply,.spz'
+      return '.obj,.stl,.ply,.spz,.fbx,.gltf,.glb,.splat,.ksplat'
🤖 Prompt for AI Agents
In @src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
around lines 211-212, The accept list for the 'mesh' option is missing several
supported 3D formats; update the returned string in the WidgetSelectDropdown.vue
branch that handles case 'mesh' (the return '.obj,.stl,.ply,.spz' expression) to
include the additional extensions .gltf, .glb, .fbx, .splat, and .ksplat so the
frontend matches the backend supported formats.

Comment on lines +314 to 332
const values = props.widget.options.values as string[]
// Reverse uploadedPaths so the very last uploaded is at absolute top if multiple
uploadedPaths.reverse().forEach((path) => {
// Remove existing duplicates to move them to top
const existingIndex = values.indexOf(path)
if (existingIndex > -1) {
values.splice(existingIndex, 1)
}
values.unshift(path)
})
}
// Enforce limit of 12
if (values.length > 12) {
values.splice(12)
}
}
// 3. Update widget value to the first uploaded file
modelValue.value = uploadedPaths[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Fix type safety and array mutation bugs in upload handling.

This code has multiple critical issues identified in previous reviews:

  1. Type safety: Line 314 casts to string[] without validation
  2. Logic bug: Line 317 mutates uploadedPaths in place with reverse(), causing modelValue (line 332) to reference the wrong element (last uploaded instead of first)
  3. Performance: O(n²) complexity with nested operations

For uploadedPaths = ['a.obj', 'b.obj', 'c.obj']:

  • After in-place reverse(): ['c.obj', 'b.obj', 'a.obj']
  • After forEach: values = ['a.obj', 'b.obj', 'c.obj', ...]
  • modelValue = uploadedPaths[0] = 'c.obj' ❌ (should be 'a.obj')
🔎 Proposed fix
     if (props.widget.options?.values) {
-      const values = props.widget.options.values as string[]
+      const rawValues = props.widget.options.values
+      if (!Array.isArray(rawValues)) {
+        return
+      }
+      
+      const values = rawValues.filter((v): v is string => typeof v === 'string')
+      const uploadedSet = new Set(uploadedPaths)
+      const filtered = values.filter((v) => !uploadedSet.has(v))
+      const updated = [...uploadedPaths, ...filtered].slice(0, 12)

-      // Reverse uploadedPaths so the very last uploaded is at absolute top if multiple
-      uploadedPaths.reverse().forEach((path) => {
-        // Remove existing duplicates to move them to top
-        const existingIndex = values.indexOf(path)
-        if (existingIndex > -1) {
-          values.splice(existingIndex, 1)
-        }
-        values.unshift(path)
-      })
-
-      // Enforce limit of 12
-      if (values.length > 12) {
-        values.splice(12)
-      }
+      props.widget.options.values = updated
     }
-    // 3. Update widget value to the first uploaded file
     modelValue.value = uploadedPaths[0]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const values = props.widget.options.values as string[]
// Reverse uploadedPaths so the very last uploaded is at absolute top if multiple
uploadedPaths.reverse().forEach((path) => {
// Remove existing duplicates to move them to top
const existingIndex = values.indexOf(path)
if (existingIndex > -1) {
values.splice(existingIndex, 1)
}
values.unshift(path)
})
}
// Enforce limit of 12
if (values.length > 12) {
values.splice(12)
}
}
// 3. Update widget value to the first uploaded file
modelValue.value = uploadedPaths[0]
if (props.widget.options?.values) {
const rawValues = props.widget.options.values
if (!Array.isArray(rawValues)) {
return
}
const values = rawValues.filter((v): v is string => typeof v === 'string')
const uploadedSet = new Set(uploadedPaths)
const filtered = values.filter((v) => !uploadedSet.has(v))
const updated = [...uploadedPaths, ...filtered].slice(0, 12)
props.widget.options.values = updated
}
modelValue.value = uploadedPaths[0]
🤖 Prompt for AI Agents
In @src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
around lines 314-332, The upload handling mutates uploadedPaths with reverse(),
casts props.widget.options.values to string[] without checking, and does O(n²)
splices; fix by: validate props.widget.options.values and uploadedPaths using
Array.isArray and filter to strings before use (refer to
props.widget.options.values and uploadedPaths), work on non-mutating copies
(e.g., const uploads = [...uploadedPathsFiltered]) and reverse the copy (not
uploadedPaths) so original order is preserved, dedupe using a Set to avoid
repeated indexOf/splice (avoid O(n²)) while building the new values array,
enforce the 12-item cap on the new array, and finally set modelValue.value to
the true first uploaded item from the validated uploads array (e.g., uploads[0])
so it references the first uploaded file.

@kaili-yang kaili-yang closed this Jan 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant