-
Notifications
You must be signed in to change notification settings - Fork 461
Feat: update the load 3d widget #7842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughRenames 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
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
Possibly related PRs
✨ Finishing touches
📜 Recent review detailsConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro 📒 Files selected for processing (1)
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. Comment |
There was a problem hiding this 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
📒 Files selected for processing (4)
src/extensions/core/load3d/Load3DConfiguration.tssrc/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/extensions/core/load3d/Load3DConfiguration.tssrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/extensions/core/load3d/Load3DConfiguration.tssrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/extensions/core/load3d/Load3DConfiguration.tssrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
**/*.vue: Use Vue 3.5+ with TypeScript in.vuefiles, 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 usewithDefaultsor runtime props declaration
Prefercomputed()overrefwithwatchwhen deriving values
PreferuseModelover separately defining prop and emit for two-way binding
Usevue-i18nin composition API for string literals; place new translation entries insrc/locales/en/main.json
Usecn()utility function from@/utils/tailwindUtilfor merging Tailwind class names; do not use:class="[]"syntax
Do not use thedark:Tailwind variant; use semantic values from thestyle.csstheme instead (e.g.,bg-node-component-surface)
Do not use!importantor the!important prefix for Tailwind classes; find and correct interfering!importantclasses 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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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 useanytype oras anytype 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.vuesrc/extensions/core/load3d/Load3DConfiguration.tssrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/extensions/core/load3d/Load3DConfiguration.tssrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/extensions/core/load3d/Load3DConfiguration.tssrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/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_filewidget- Configures the descriptor with appropriate
kind,allowUpload,folder, andsubfoldervalues- Uses a sensible default subfolder (
'3d') for organizing 3D model assets- Overrides folder to
'input'which aligns with backend expectationsThe logic is clean, specific, and well-integrated with the existing asset handling system.
| if (setting.modelWidget.options?.values) { | ||
| let values = setting.modelWidget.options.values as string[] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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> |
There was a problem hiding this comment.
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.
| 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) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add type guard and simplify array manipulation.
The code has two concerns:
- Type safety: Casts
props.widget.options.valuestostring[]without verifying it's actually an array of strings (similar issue as in Load3DConfiguration.ts). - Efficiency: The
reverse().forEach()withspliceandunshiftoperations 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.
| 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).
There was a problem hiding this 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:
- Type Safety: Line 312 casts to
string[]without verifying the array actually contains strings, which could cause runtime errors.- Performance: The
reverse().forEach()withindexOf/splice/unshiftpattern 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
Setfor 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 fromstyle.css, but the concerns have not been addressed.Per coding guidelines: "Use semantic values from the
style.csstheme 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-backgroundandtext-muted-foreground, and using a Tailwind utility class instead of the inlinefont-sizestyle.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
📒 Files selected for processing (2)
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuItem.vue
**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
**/*.vue: Use Vue 3.5+ with TypeScript in.vuefiles, 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 usewithDefaultsor runtime props declaration
Prefercomputed()overrefwithwatchwhen deriving values
PreferuseModelover separately defining prop and emit for two-way binding
Usevue-i18nin composition API for string literals; place new translation entries insrc/locales/en/main.json
Usecn()utility function from@/utils/tailwindUtilfor merging Tailwind class names; do not use:class="[]"syntax
Do not use thedark:Tailwind variant; use semantic values from thestyle.csstheme instead (e.g.,bg-node-component-surface)
Do not use!importantor the!important prefix for Tailwind classes; find and correct interfering!importantclasses 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.vuesrc/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 useanytype oras anytype 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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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
uploadSubfolderprop is well-integrated with the existinguploadFolderprop 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
uploadSubfolderprop is properly extracted and passed touploadFile, maintaining the upload flow's integrity.
| case 'model': | ||
| return '.obj,.stl,.ply,.spz' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -20Repository: 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 -50Repository: 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 -B2Repository: 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 -10Repository: Comfy-Org/ComfyUI_frontend
Length of output: 102
🏁 Script executed:
#!/bin/bash
# Search for .spz format references
rg -n "spz" -iRepository: 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/nullRepository: 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 -B2Repository: 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 -B3Repository: 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.
There was a problem hiding this 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
📒 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.vuefiles, 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 usewithDefaultsor runtime props declaration
Prefercomputed()overrefwithwatchwhen deriving values
PreferuseModelover separately defining prop and emit for two-way binding
Usevue-i18nin composition API for string literals; place new translation entries insrc/locales/en/main.json
Usecn()utility function from@/utils/tailwindUtilfor merging Tailwind class names; do not use:class="[]"syntax
Do not use thedark:Tailwind variant; use semantic values from thestyle.csstheme instead (e.g.,bg-node-component-surface)
Do not use!importantor the!important prefix for Tailwind classes; find and correct interfering!importantclasses 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 useanytype oras anytype 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-subfolderprop is correctly wired to theWidgetSelectDropdowncomponent, following Vue's kebab-case convention for template prop bindings.
76-82: LGTM!The fallback
|| {}for destructuring safely handles the case wherespecis undefined (whenisLoad3DModelis true but no comboSpec exists), preventing runtime errors while keeping the code concise.
97-112: LGTM!The
allowUploadextension and folder/subfolder logic correctly enable 3D model uploads to theinput/3ddirectory. The implementation is consistent with the existing patterns for other asset types.
137-137: LGTM!The
uploadSubfoldercomputed 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 theAssetKindtype, so no TypeScript compilation errors will occur.
| const isLoad3DModel = | ||
| props.nodeType === 'Load3D' && props.widget.name === 'model_file' | ||
| const spec = comboSpec.value | ||
| if (!spec) { | ||
| if (!spec && !isLoad3DModel) { |
There was a problem hiding this comment.
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_NAMECommittable 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.
There was a problem hiding this 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()withspliceandunshifthas not been resolved.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 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.vuefiles, 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 usewithDefaultsor runtime props declaration
Prefercomputed()overrefwithwatchwhen deriving values
PreferuseModelover separately defining prop and emit for two-way binding
Usevue-i18nin composition API for string literals; place new translation entries insrc/locales/en/main.json
Usecn()utility function from@/utils/tailwindUtilfor merging Tailwind class names; do not use:class="[]"syntax
Do not use thedark:Tailwind variant; use semantic values from thestyle.csstheme instead (e.g.,bg-node-component-surface)
Do not use!importantor the!important prefix for Tailwind classes; find and correct interfering!importantclasses 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 useanytype oras anytype 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
uploadSubfolderprop 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
isPastedcondition over theformFields.subfolder, and the FormData append logic is sound.
288-296: LGTM!The subfolder prop is correctly propagated through to the
uploadFilefunction.
| 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) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
|
Closing temporarily — issues identified and improvements needed. Will revisit. |
There was a problem hiding this 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
📒 Files selected for processing (6)
src/composables/useLoad3d.tssrc/extensions/core/load3d.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/composables/useLoad3d.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/types/widgetTypes.tssrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/composables/useLoad3d.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/types/widgetTypes.tssrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/composables/useLoad3d.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/types/widgetTypes.tssrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/composables/useLoad3d.ts
**/*.vue
📄 CodeRabbit inference engine (AGENTS.md)
**/*.vue: Use Vue 3.5+ with TypeScript in.vuefiles, 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 usewithDefaultsor runtime props declaration
Prefercomputed()overrefwithwatchwhen deriving values
PreferuseModelover separately defining prop and emit for two-way binding
Usevue-i18nin composition API for string literals; place new translation entries insrc/locales/en/main.json
Usecn()utility function from@/utils/tailwindUtilfor merging Tailwind class names; do not use:class="[]"syntax
Do not use thedark:Tailwind variant; use semantic values from thestyle.csstheme instead (e.g.,bg-node-component-surface)
Do not use!importantor the!important prefix for Tailwind classes; find and correct interfering!importantclasses 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.vuesrc/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 useanytype oras anytype 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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/composables/useLoad3d.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/types/widgetTypes.tssrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/composables/useLoad3d.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/types/widgetTypes.tssrc/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.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/types/widgetTypes.tssrc/extensions/core/load3d.ts
src/**/{services,composables}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (src/CLAUDE.md)
src/**/{services,composables}/**/*.{ts,tsx}: Useapi.apiURL()for backend endpoints instead of constructing URLs directly
Useapi.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.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/types/widgetTypes.tssrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/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.vuesrc/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vuesrc/composables/useLoad3d.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/types/widgetTypes.tssrc/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.vuesrc/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.vuesrc/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.vuesrc/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.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/types/widgetTypes.tssrc/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.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/types/widgetTypes.tssrc/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.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/types/widgetTypes.tssrc/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.tssrc/extensions/core/load3d/Load3DConfiguration.tssrc/types/widgetTypes.tssrc/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
getMeshUrlfunction 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
uploadSubfolderprop 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
modelWidgettomeshWidgetis consistent throughouthandleModelUpload. 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
uploadSubfolderprop 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
modelWidgettomeshWidgetaligns 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
| 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 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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') |
There was a problem hiding this comment.
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 classThen 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| const isLoad3DMesh = | ||
| props.nodeType === 'Load3D' && props.widget.name === 'model_file' |
There was a problem hiding this comment.
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.
| 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.
| case 'mesh': | ||
| return '.obj,.stl,.ply,.spz' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Fix type safety and array mutation bugs in upload handling.
This code has multiple critical issues identified in previous reviews:
- Type safety: Line 314 casts to
string[]without validation - Logic bug: Line 317 mutates
uploadedPathsin place withreverse(), causingmodelValue(line 332) to reference the wrong element (last uploaded instead of first) - 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.
| 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.

Summary
Enable the rich
WidgetSelectDropdowncomponent for themodel_fileinput 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
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vueandsrc/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vueReview 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