Skip to content

Conversation

@jtydhr88
Copy link
Collaborator

@jtydhr88 jtydhr88 commented Jan 2, 2026

Summary

Another implementation for image crop node, alternative for #7014
As discussed with @christian-byrne and @DrJKL we could have single widget - IMAGECROP with 4 ints and UI preview.

However, this solution requires changing the definition of image crop node in BE (sent here), which will break the exsiting workflow, also it would not allow connect separate int node as input, I am not sure it is a good idea.

So I keep two PRs openned for references

Screenshots

2025-12-31.23-16-23.mp4

┆Issue is synchronized with this Notion page by Unito

@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Jan 2, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 2, 2026

📝 Walkthrough

Walkthrough

Adds an interactive image-cropping feature: a Vue 3 component and composable with drag/resize crop interactions and v-model CropRegion; registers a new imagecrop widget type, LiteGraph canvas widget, input schema, extension enforcing node minimum size, widget constructor/registry, and i18n strings.

Changes

Cohort / File(s) Summary
Vue Component & Composable
src/components/imagecrop/WidgetImageCrop.vue, src/composables/useImageCrop.ts
New Vue SFC and composable implementing image crop UI and logic. Exposes v-model CropRegion, image state, computed styles, resize-handle positions, and drag/resize/load/error handlers; syncs with node outputs.
Node Extension
src/extensions/core/imageCrop.ts, src/extensions/core/index.ts
New core extension registers "Comfy.ImageCrop" and enforces minimum node dimensions on creation (min width 300, min height 450).
Widget Types & Canvas Widget
src/lib/litegraph/src/types/widgets.ts, src/lib/litegraph/src/widgets/ImageCropWidget.ts, src/lib/litegraph/src/widgets/widgetMap.ts
Adds CropRegionValue and IImageCropWidget, implements ImageCropWidget (canvas preview drawing), and maps 'imagecrop' in widgetMap.
Widget Registry & Constructor
src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts, src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
Registers async WidgetImageCrop component; provides useImageCropWidget constructor (default size {width:512,height:512}, serialize: true) returning an imagecrop widget.
Public Widget API
src/scripts/widgets.ts
Adds IMAGECROP to exported ComfyWidgets, wrapping the V2 constructor for V1 compatibility.
Input Schema
src/schemas/nodeDef/nodeDefSchemaV2.ts
Adds zImageCropInputSpec (type 'IMAGECROP') and exports ImageCropInputSpec; includes it in the zInputSpec union.
Localization
src/locales/en/main.json
Adds imageCrop i18n object with keys loading, noInputImage, and cropPreviewAlt.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Vue as WidgetImageCrop.vue
    participant Composable as useImageCrop
    participant Store as NodeOutputStore
    participant Model as CropRegion (v-model)

    rect rgb(250,250,250)
      Note over User,Model: Initialization & render
      User->>Vue: mount(nodeId)
      Vue->>Composable: useImageCrop(nodeId)
      Composable->>Store: subscribe/read image URL
      Store-->>Composable: image URL
      Composable->>Vue: provide reactive state/styles
      Vue-->>User: render preview + controls
    end

    rect rgb(230,245,255)
      Note over User,Model: Drag move flow
      User->>Vue: pointerdown on crop box
      Vue->>Composable: handleDragStart()
      User->>Vue: pointermove
      Vue->>Composable: handleDragMove()
      Composable->>Composable: clamp & update x,y
      Composable-->>Model: emit updated CropRegion
      Model-->>Vue: v-model sync
      User->>Vue: pointerup
      Vue->>Composable: handleDragEnd()
    end

    rect rgb(235,255,235)
      Note over User,Model: Resize handle flow
      User->>Vue: pointerdown on resize handle
      Vue->>Composable: handleResizeStart(handle)
      User->>Vue: pointermove
      Vue->>Composable: handleResizeMove()
      Composable->>Composable: enforce min size & clamp
      Composable-->>Model: emit updated CropRegion
      User->>Vue: pointerup
      Vue->>Composable: handleResizeEnd()
    end
Loading
✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

@github-actions
Copy link

github-actions bot commented Jan 2, 2026

🎭 Playwright Test Results

⚠️ Tests passed with flaky tests

⏰ Completed at: 01/06/2026, 03:27:02 AM UTC

📈 Summary

  • Total Tests: 513
  • Passed: 504 ✅
  • Failed: 0
  • Flaky: 1 ⚠️
  • Skipped: 8 ⏭️

📊 Test Reports by Browser

  • chromium: View Report • ✅ 494 / ❌ 0 / ⚠️ 0 / ⏭️ 8
  • chromium-2x: View Report • ✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0
  • chromium-0.5x: View Report • ✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0
  • mobile-chrome: View Report • ✅ 7 / ❌ 0 / ⚠️ 1 / ⏭️ 0

🎉 Click on the links above to view detailed test results for each browser configuration.

@github-actions
Copy link

github-actions bot commented Jan 2, 2026

🎨 Storybook Build Status

Build completed successfully!

⏰ Completed at: 01/06/2026, 03:21:39 AM UTC

🔗 Links


🎉 Your Storybook is ready for review!

@github-actions
Copy link

github-actions bot commented Jan 2, 2026

Bundle Size Report

Summary

  • Raw size: 17.5 MB baseline 17.4 MB — 🔴 +25.1 kB
  • Gzip: 3.57 MB baseline 3.57 MB — 🔴 +4.91 kB
  • Brotli: 2.72 MB baseline 2.72 MB — 🔴 +4.02 kB
  • Bundles: 100 current • 99 baseline • 44 added / 43 removed

Category Glance
Other 🔴 +22.7 kB (3.53 MB) · App Entry Points 🔴 +2.38 kB (3.23 MB) · Vendor & Third-Party ⚪ 0 B (9.19 MB) · Graph Workspace ⚪ 0 B (1 MB) · Panels & Settings ⚪ 0 B (300 kB) · UI Components ⚪ 0 B (193 kB) · + 3 more

Per-category breakdown
App Entry Points — 3.23 MB (baseline 3.23 MB) • 🔴 +2.38 kB

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-Cj_5IF_5.js (new) 3.04 MB 🔴 +3.04 MB 🔴 +637 kB 🔴 +484 kB
assets/index-BPBrN_6c.js (removed) 3.03 MB 🟢 -3.03 MB 🟢 -637 kB 🟢 -484 kB
assets/index-DhSgD2Lm.js (new) 194 kB 🔴 +194 kB 🔴 +42.3 kB 🔴 +35.2 kB
assets/index-BVMNhNiC.js (removed) 194 kB 🟢 -194 kB 🟢 -42.3 kB 🟢 -35.2 kB
assets/index-mToeHv6P.js (removed) 345 B 🟢 -345 B 🟢 -244 B 🟢 -202 B
assets/index-W4kShNlk.js (new) 345 B 🔴 +345 B 🔴 +247 B 🔴 +228 B

Status: 3 added / 3 removed

Graph Workspace — 1 MB (baseline 1 MB) • ⚪ 0 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-D8fGyhg6.js (new) 1 MB 🔴 +1 MB 🔴 +195 kB 🔴 +148 kB
assets/GraphView-IppbCtkq.js (removed) 1 MB 🟢 -1 MB 🟢 -195 kB 🟢 -148 kB

Status: 1 added / 1 removed

Views & Navigation — 6.63 kB (baseline 6.63 kB) • ⚪ 0 B

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/UserSelectView-2scNTZm3.js (new) 6.63 kB 🔴 +6.63 kB 🔴 +2.14 kB 🔴 +1.9 kB
assets/UserSelectView-CgjUwl_P.js (removed) 6.63 kB 🟢 -6.63 kB 🟢 -2.14 kB 🟢 -1.9 kB

Status: 1 added / 1 removed

Panels & Settings — 300 kB (baseline 300 kB) • ⚪ 0 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/LegacyCreditsPanel-Cj3aQDBl.js (removed) 22.7 kB 🟢 -22.7 kB 🟢 -5.25 kB 🟢 -4.6 kB
assets/LegacyCreditsPanel-DNAxgLES.js (new) 22.7 kB 🔴 +22.7 kB 🔴 +5.25 kB 🔴 +4.61 kB
assets/KeybindingPanel-C6eQRYOd.js (new) 14.8 kB 🔴 +14.8 kB 🔴 +3.57 kB 🔴 +3.12 kB
assets/KeybindingPanel-DuqjtH2l.js (removed) 14.8 kB 🟢 -14.8 kB 🟢 -3.57 kB 🟢 -3.12 kB
assets/ExtensionPanel-B3-CbkLq.js (new) 11.1 kB 🔴 +11.1 kB 🔴 +2.62 kB 🔴 +2.29 kB
assets/ExtensionPanel-BIntGJRu.js (removed) 11.1 kB 🟢 -11.1 kB 🟢 -2.62 kB 🟢 -2.29 kB
assets/AboutPanel--Uv2zlD4.js (removed) 9.16 kB 🟢 -9.16 kB 🟢 -2.46 kB 🟢 -2.21 kB
assets/AboutPanel-BScWQyVS.js (new) 9.16 kB 🔴 +9.16 kB 🔴 +2.46 kB 🔴 +2.2 kB
assets/ServerConfigPanel-Ce7Q02zU.js (removed) 7.51 kB 🟢 -7.51 kB 🟢 -2.04 kB 🟢 -1.81 kB
assets/ServerConfigPanel-idSR4dgI.js (new) 7.51 kB 🔴 +7.51 kB 🔴 +2.04 kB 🔴 +1.81 kB
assets/UserPanel-CO70eNX0.js (removed) 6.88 kB 🟢 -6.88 kB 🟢 -1.79 kB 🟢 -1.56 kB
assets/UserPanel-REpLtOhR.js (new) 6.88 kB 🔴 +6.88 kB 🔴 +1.79 kB 🔴 +1.57 kB
assets/settings-BhbWhsRg.js 101 B 101 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BIdKi-OT.js 26.2 kB 26.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Bu3OR-lX.js 24.6 kB 24.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-ByL6gy5c.js 25.4 kB 25.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CjlRFMdL.js 32.8 kB 32.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DkGwvylK.js 26.9 kB 26.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Dyd027Dx.js 24.7 kB 24.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-MzsBgiwB.js 21.7 kB 21.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-wwBxqLH5.js 21.3 kB 21.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-xx2Yb6R2.js 23.8 kB 23.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 6 added / 6 removed

UI Components — 193 kB (baseline 193 kB) • ⚪ 0 B

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/LazyImage.vue_vue_type_script_setup_true_lang-CY9JNDgR.js (removed) 60.1 kB 🟢 -60.1 kB 🟢 -12.4 kB 🟢 -10.8 kB
assets/LazyImage.vue_vue_type_script_setup_true_lang-DZeCzMYd.js (new) 60.1 kB 🔴 +60.1 kB 🔴 +12.4 kB 🔴 +10.8 kB
assets/Load3D.vue_vue_type_script_setup_true_lang-BLEQ6g91.js (removed) 54.4 kB 🟢 -54.4 kB 🟢 -8.6 kB 🟢 -7.37 kB
assets/Load3D.vue_vue_type_script_setup_true_lang-p4KLZIEo.js (new) 54.4 kB 🔴 +54.4 kB 🔴 +8.6 kB 🔴 +7.37 kB
assets/WidgetSelect.vue_vue_type_script_setup_true_lang-B4X_MsrR.js (new) 49 kB 🔴 +49 kB 🔴 +10.5 kB 🔴 +9.15 kB
assets/WidgetSelect.vue_vue_type_script_setup_true_lang-C42cSLP-.js (removed) 49 kB 🟢 -49 kB 🟢 -10.5 kB 🟢 -9.14 kB
assets/WidgetInputNumber.vue_vue_type_script_setup_true_lang-CM-INqi0.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -2.89 kB 🟢 -2.56 kB
assets/WidgetInputNumber.vue_vue_type_script_setup_true_lang-rzpNt6zp.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +2.9 kB 🔴 +2.55 kB
assets/ComfyQueueButton-BR5TKBED.js (new) 8.83 kB 🔴 +8.83 kB 🔴 +2.58 kB 🔴 +2.3 kB
assets/ComfyQueueButton-XcfAXEYw.js (removed) 8.83 kB 🟢 -8.83 kB 🟢 -2.58 kB 🟢 -2.29 kB
assets/WidgetWithControl.vue_vue_type_script_setup_true_lang-BqxjX0ST.js (new) 3.72 kB 🔴 +3.72 kB 🔴 +1.46 kB 🔴 +1.32 kB
assets/WidgetWithControl.vue_vue_type_script_setup_true_lang-m7JyoBJV.js (removed) 3.72 kB 🟢 -3.72 kB 🟢 -1.45 kB 🟢 -1.32 kB
assets/WidgetButton-CqoirYW8.js (removed) 2.21 kB 🟢 -2.21 kB 🟢 -995 B 🟢 -895 B
assets/WidgetButton-DNsAimjq.js (new) 2.21 kB 🔴 +2.21 kB 🔴 +996 B 🔴 +894 B
assets/WidgetLayoutField.vue_vue_type_script_setup_true_lang-_UHmw8Dj.js (new) 2.14 kB 🔴 +2.14 kB 🔴 +890 B 🔴 +776 B
assets/WidgetLayoutField.vue_vue_type_script_setup_true_lang-CzxREDe0.js (removed) 2.14 kB 🟢 -2.14 kB 🟢 -890 B 🟢 -770 B
assets/MediaTitle.vue_vue_type_script_setup_true_lang-DD8hmkZI.js (removed) 897 B 🟢 -897 B 🟢 -501 B 🟢 -433 B
assets/MediaTitle.vue_vue_type_script_setup_true_lang-ZIRsKZWZ.js (new) 897 B 🔴 +897 B 🔴 +502 B 🔴 +435 B
assets/UserAvatar.vue_vue_type_script_setup_true_lang-Btc-ocVv.js 1.34 kB 1.34 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 9 added / 9 removed

Data & Services — 12.5 kB (baseline 12.5 kB) • ⚪ 0 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/keybindingService-DPgD6RwE.js (removed) 7.51 kB 🟢 -7.51 kB 🟢 -1.83 kB 🟢 -1.57 kB
assets/keybindingService-k4yAC_EP.js (new) 7.51 kB 🔴 +7.51 kB 🔴 +1.83 kB 🔴 +1.59 kB
assets/audioService-DbsgmAQD.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +962 B 🔴 +827 B
assets/audioService-MKXxZUq7.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -965 B 🟢 -827 B
assets/serverConfigStore-CLS4uKu9.js 2.83 kB 2.83 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 2 added / 2 removed

Utilities & Hooks — 1.41 kB (baseline 1.41 kB) • ⚪ 0 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/audioUtils-DjLjiQ_e.js (removed) 1.41 kB 🟢 -1.41 kB 🟢 -650 B 🟢 -546 B
assets/audioUtils-DTW-nsGL.js (new) 1.41 kB 🔴 +1.41 kB 🔴 +652 B 🔴 +547 B

Status: 1 added / 1 removed

Vendor & Third-Party — 9.19 MB (baseline 9.19 MB) • ⚪ 0 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-chart-vIZEp4HX.js 452 kB 452 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-other-fFl5a9Y4.js 3.9 MB 3.9 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-primevue-woBPd4cb.js 1.95 MB 1.95 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-B_Dw2uOl.js 2.08 MB 2.08 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-C79JtMXE.js 232 kB 232 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-BXnt3M34.js 160 kB 160 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-BF8peZ5_.js 420 kB 420 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Other — 3.53 MB (baseline 3.5 MB) • 🔴 +22.7 kB

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SubscriptionRequiredDialogContent-BfD6tqao.js (new) 29 kB 🔴 +29 kB 🔴 +6.42 kB 🔴 +5.58 kB
assets/SubscriptionRequiredDialogContent-DWIgFRwj.js (removed) 29 kB 🟢 -29 kB 🟢 -6.42 kB 🟢 -5.58 kB
assets/WidgetImageCrop-CohQDgid.js (new) 22.7 kB 🔴 +22.7 kB 🔴 +4.63 kB 🔴 +4.03 kB
assets/WidgetRecordAudio-26ER9qRe.js (removed) 20.4 kB 🟢 -20.4 kB 🟢 -5.24 kB 🟢 -4.63 kB
assets/WidgetRecordAudio-CjmRkIif.js (new) 20.4 kB 🔴 +20.4 kB 🔴 +5.24 kB 🔴 +4.63 kB
assets/AudioPreviewPlayer-D25Y3h4w.js (removed) 13.3 kB 🟢 -13.3 kB 🟢 -3.35 kB 🟢 -3 kB
assets/AudioPreviewPlayer-DNZLvf2N.js (new) 13.3 kB 🔴 +13.3 kB 🔴 +3.35 kB 🔴 +2.99 kB
assets/ValueControlPopover-CXsHQKwW.js (removed) 5.49 kB 🟢 -5.49 kB 🟢 -1.71 kB 🟢 -1.52 kB
assets/ValueControlPopover-DkEUXVAr.js (new) 5.49 kB 🔴 +5.49 kB 🔴 +1.71 kB 🔴 +1.51 kB
assets/WidgetGalleria-BvW9T-2m.js (removed) 4.1 kB 🟢 -4.1 kB 🟢 -1.44 kB 🟢 -1.3 kB
assets/WidgetGalleria-D0AI9x9M.js (new) 4.1 kB 🔴 +4.1 kB 🔴 +1.45 kB 🔴 +1.3 kB
assets/WidgetColorPicker-BoiEBLyV.js (new) 3.41 kB 🔴 +3.41 kB 🔴 +1.38 kB 🔴 +1.23 kB
assets/WidgetColorPicker-CZ6nuOo5.js (removed) 3.41 kB 🟢 -3.41 kB 🟢 -1.38 kB 🟢 -1.23 kB
assets/WidgetTextarea-BNALnbhy.js (new) 3.08 kB 🔴 +3.08 kB 🔴 +1.22 kB 🔴 +1.07 kB
assets/WidgetTextarea-CrwE8iZm.js (removed) 3.08 kB 🟢 -3.08 kB 🟢 -1.21 kB 🟢 -1.08 kB
assets/WidgetMarkdown-CuJEBQZ8.js (new) 3.08 kB 🔴 +3.08 kB 🔴 +1.28 kB 🔴 +1.12 kB
assets/WidgetMarkdown-p2sIcqsD.js (removed) 3.08 kB 🟢 -3.08 kB 🟢 -1.28 kB 🟢 -1.12 kB
assets/WidgetAudioUI-DuoSbloB.js (new) 2.89 kB 🔴 +2.89 kB 🔴 +1.17 kB 🔴 +1.06 kB
assets/WidgetAudioUI-DxTHGq7Y.js (removed) 2.89 kB 🟢 -2.89 kB 🟢 -1.17 kB 🟢 -1.06 kB
assets/WidgetInputText-C81E6CX1.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +920 B 🔴 +855 B
assets/WidgetInputText-DN8SVpc9.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -919 B 🟢 -846 B
assets/WidgetToggleSwitch-ClpTSBNF.js (new) 1.76 kB 🔴 +1.76 kB 🔴 +836 B 🔴 +734 B
assets/WidgetToggleSwitch-OWmzgwQ3.js (removed) 1.76 kB 🟢 -1.76 kB 🟢 -836 B 🟢 -733 B
assets/MediaImageBottom-DAADUja5.js (removed) 1.55 kB 🟢 -1.55 kB 🟢 -734 B 🟢 -637 B
assets/MediaImageBottom-LeKvgiFE.js (new) 1.55 kB 🔴 +1.55 kB 🔴 +729 B 🔴 +641 B
assets/MediaAudioBottom-CvZmFgM4.js (removed) 1.51 kB 🟢 -1.51 kB 🟢 -733 B 🟢 -652 B
assets/MediaAudioBottom-T3hSqAV4.js (new) 1.51 kB 🔴 +1.51 kB 🔴 +733 B 🔴 +648 B
assets/Media3DBottom-BU29EHhU.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +733 B 🔴 +646 B
assets/Media3DBottom-Y0uz_W3s.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -732 B 🟢 -646 B
assets/MediaVideoBottom-BWNUTa0b.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -732 B 🟢 -647 B
assets/MediaVideoBottom-ky5-WSMX.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +732 B 🔴 +646 B
assets/Media3DTop-BLAM52Kl.js (new) 1.49 kB 🔴 +1.49 kB 🔴 +764 B 🔴 +648 B
assets/Media3DTop-CmHVZmM2.js (removed) 1.49 kB 🟢 -1.49 kB 🟢 -764 B 🟢 -652 B
assets/WidgetSelect-CpiGrwc-.js (removed) 733 B 🟢 -733 B 🟢 -358 B 🟢 -324 B
assets/WidgetSelect-DkHFk2K2.js (new) 733 B 🔴 +733 B 🔴 +363 B 🔴 +333 B
assets/WidgetInputNumber-DXKdT2gp.js (removed) 673 B 🟢 -673 B 🟢 -351 B 🟢 -293 B
assets/WidgetInputNumber-qnD2Bx4F.js (new) 673 B 🔴 +673 B 🔴 +351 B 🔴 +289 B
assets/Load3D-CrJtWzb8.js (new) 424 B 🔴 +424 B 🔴 +266 B 🔴 +223 B
assets/Load3D-DW6LIz0S.js (removed) 424 B 🟢 -424 B 🟢 -268 B 🟢 -224 B
assets/WidgetLegacy-B05Fj0qp.js (new) 364 B 🔴 +364 B 🔴 +236 B 🔴 +197 B
assets/WidgetLegacy-wEJn9VV4.js (removed) 364 B 🟢 -364 B 🟢 -236 B 🟢 -195 B
assets/commands-09qoDJrw.js 13.8 kB 13.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BWp4HdfU.js 101 B 101 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CcfGaui5.js 14.4 kB 14.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CisfgZf5.js 13.7 kB 13.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CkU12Foh.js 13 kB 13 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CoH2DJa6.js 14.2 kB 14.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-COSt-Bjx.js 14.9 kB 14.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DalfIW5f.js 15.9 kB 15.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DfTl0eCm.js 13.5 kB 13.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DwSJL865.js 13.7 kB 13.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Ba_fO77I.js 91.3 kB 91.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bdc58rJq.js 97.1 kB 97.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-C9ZJBRdI.js 81.5 kB 81.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CAL83XT3.js 84.6 kB 84.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CHLLfvpG.js 82.4 kB 82.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Cw9RZWRY.js 89 B 89 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DDqR5EuX.js 71.3 kB 71.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DLHyaEcz.js 92.1 kB 92.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-O7KfJeMO.js 79.9 kB 79.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-OzGsrlqJ.js 112 kB 112 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-DrF_Vdi7.js 1.46 kB 1.46 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-DmgiGEEz.js 1.75 kB 1.75 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-BXyavTZf.js 2.65 kB 2.65 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-aW9En70v.js 260 kB 260 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BIckSVgU.js 273 kB 273 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BiYpVi7D.js 263 kB 263 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Bw_Jitw_.js 101 B 101 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CCEXtYfM.js 243 kB 243 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CvmVDWYd.js 323 kB 323 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-D_wreoPJ.js 267 kB 267 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Dz-0ZIBN.js 297 kB 297 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-VZsNmhG7.js 264 kB 264 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Zy145v5w.js 279 kB 279 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2Worker-GDlTeC1j.js 4.69 kB 4.69 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-dhwVJAgU.js 2.48 kB 2.48 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-C2io158s.js 3.18 kB 3.18 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-BIbGSUAt.js 1.28 kB 1.28 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 21 added / 20 removed

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e932bb and 91fc236.

📒 Files selected for processing (12)
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/composables/useImageCrop.ts
  • src/extensions/core/imageCrop.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/types/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/locales/en/main.json
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
🧰 Additional context used
📓 Path-based instructions (19)
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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/extensions/core/index.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/extensions/core/index.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/extensions/core/index.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
src/**/*.{vue,ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/extensions/core/index.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
src/lib/litegraph/**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (src/lib/litegraph/CLAUDE.md)

src/lib/litegraph/**/*.{js,ts,jsx,tsx}: Run ESLint instead of manually figuring out whitespace fixes or other trivial style concerns using the pnpm lint:fix command
Take advantage of TypedArray subarray when appropriate
The size and pos properties of Rectangle share the same array buffer (subarray); they may be used to set the rectangle's size and position
Prefer single line if syntax over adding curly braces, when the statement has a very concise expression and concise, single line statement
Do not replace &&= or ||= with = when there is no reason to do so. If you do find a reason to remove either &&= or ||=, leave a comment explaining why the removal occurred
When writing methods, prefer returning idiomatic JavaScript undefined over null

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
src/lib/litegraph/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/lib/litegraph/CLAUDE.md)

Type assertions are an absolute last resort. In almost all cases, they are a crutch that leads to brittle code

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/extensions/core/index.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/locales/en/main.json
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/extensions/core/index.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/extensions/core/index.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
src/**/{services,composables}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.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/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/components/imagecrop/WidgetImageCrop.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/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/components/imagecrop/WidgetImageCrop.vue
**/**/use[A-Z]*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name composables using the pattern useXyz.ts

Files:

  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
src/**/index.ts

📄 CodeRabbit inference engine (AGENTS.md)

Do not use barrel files (e.g., /some/package/index.ts) to re-export within /src

Files:

  • src/extensions/core/index.ts
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/components/imagecrop/WidgetImageCrop.vue
src/components/**/*.vue

📄 CodeRabbit inference engine (src/components/CLAUDE.md)

src/components/**/*.vue: Use setup() function in Vue 3 Composition API
Destructure props using Vue 3.5 style in Vue components
Use ref/reactive for state management in Vue 3 Composition API
Implement computed() for derived state in Vue 3 Composition API
Use provide/inject for dependency injection in Vue components
Prefer emit/@event-name for state changes over other communication patterns
Use defineExpose only for imperative operations (such as form.validate(), modal.open())
Replace PrimeVue Dropdown component with Select
Replace PrimeVue OverlayPanel component with Popover
Replace PrimeVue Calendar component with DatePicker
Replace PrimeVue InputSwitch component with ToggleSwitch
Replace PrimeVue Sidebar component with Drawer
Replace PrimeVue Chips component with AutoComplete with multiple enabled
Replace PrimeVue TabMenu component with Tabs without panels
Replace PrimeVue Steps component with Stepper without panels
Replace PrimeVue InlineMessage component with Message
Extract complex conditionals to computed properties
Implement cleanup for async operations in Vue components
Use lifecycle hooks: onMounted, onUpdated in Vue 3 Composition API
Use Teleport/Suspense when needed for component rendering
Define proper props and emits definitions in Vue components

Files:

  • src/components/imagecrop/WidgetImageCrop.vue
src/components/**/*.{vue,css}

📄 CodeRabbit inference engine (src/components/CLAUDE.md)

src/components/**/*.{vue,css}: Use Tailwind CSS only for styling (no custom CSS)
Use the correct tokens from style.css in the design system package

Files:

  • src/components/imagecrop/WidgetImageCrop.vue
src/components/**/*.{vue,ts,js}

📄 CodeRabbit inference engine (src/components/CLAUDE.md)

src/components/**/*.{vue,ts,js}: Use existing VueUse composables (such as useElementHover) instead of manually managing event listeners
Use useIntersectionObserver for visibility detection instead of custom scroll handlers
Use vue-i18n for ALL UI strings

Files:

  • src/components/imagecrop/WidgetImageCrop.vue
**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • src/components/imagecrop/WidgetImageCrop.vue
🧠 Learnings (20)
📓 Common learnings
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:57.926Z
Learning: In src/renderer/extensions/vueNodes/components/ImagePreview.vue and LGraphNode.vue, keyboard navigation for image galleries should respond to node-level focus (via keyEvent injection from LGraphNode), not require focus within the image preview wrapper itself. This allows users to navigate the gallery with arrow keys immediately when the node is focused/selected.
📚 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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/extensions/core/index.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/extensions/core/index.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/extensions/core/index.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/extensions/core/index.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
📚 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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/extensions/core/index.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.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,ts,js} : Use existing VueUse composables (such as useElementHover) instead of manually managing event listeners

Applied to files:

  • src/composables/useImageCrop.ts
  • src/scripts/widgets.ts
📚 Learning: 2025-11-24T19:47:02.860Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T19:47:02.860Z
Learning: Applies to src/**/*.{vue,ts} : Leverage VueUse functions for performance-enhancing styles

Applied to files:

  • src/composables/useImageCrop.ts
  • src/scripts/widgets.ts
📚 Learning: 2025-12-09T04:35:43.971Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 6300
File: src/locales/en/main.json:774-780
Timestamp: 2025-12-09T04:35:43.971Z
Learning: In the Comfy-Org/ComfyUI_frontend repository, locale files other than `src/locales/en/main.json` are generated automatically on every release. Developers only need to add English (en) key/values in `src/locales/en/main.json` when making PRs; manual updates to other locale files (fr, ja, ko, ru, zh, zh-TW, es, ar, tr, etc.) are not required and should not be suggested in reviews.

Applied to files:

  • src/locales/en/main.json
📚 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/**/{components,composables}/**/*.{ts,tsx,vue} : Use vue-i18n for ALL user-facing strings by adding them to `src/locales/en/main.json`

Applied to files:

  • src/locales/en/main.json
📚 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 : Leverage VueUse functions for performance-enhancing styles in Vue components

Applied to files:

  • src/scripts/widgets.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 **/*.vue : Avoid new usage of PrimeVue components; use VueUse, shadcn/vue, or Reka UI instead

Applied to files:

  • src/scripts/widgets.ts
📚 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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.vue
📚 Learning: 2025-12-18T16:03:02.066Z
Learnt from: henrikvilhelmberglund
Repo: Comfy-Org/ComfyUI_frontend PR: 7617
File: src/components/actionbar/ComfyActionbar.vue:301-308
Timestamp: 2025-12-18T16:03:02.066Z
Learning: In the ComfyUI frontend queue system, useQueuePendingTaskCountStore().count indicates the number of tasks in the queue, where count = 1 means a single active/running task and count > 1 means there are pending tasks in addition to the active task. Therefore, in src/components/actionbar/ComfyActionbar.vue, enable the 'Clear Pending Tasks' button only when count > 1 to avoid clearing the currently running task. The active task should be canceled using the 'Cancel current run' button instead. This rule should be enforced via a conditional check on the queue count, with appropriate disabled/aria-disabled states for accessibility, and tests should verify behavior for count = 1 and count > 1.

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.vue
🧬 Code graph analysis (5)
src/extensions/core/imageCrop.ts (1)
src/services/extensionService.ts (1)
  • useExtensionService (16-215)
src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts (3)
src/scripts/widgets.ts (1)
  • ComfyWidgetConstructorV2 (31-34)
src/lib/litegraph/src/types/widgets.ts (1)
  • IImageCropWidget (266-272)
src/schemas/nodeDef/nodeDefSchemaV2.ts (1)
  • ImageCropInputSpec (180-180)
src/lib/litegraph/src/widgets/ImageCropWidget.ts (3)
src/lib/litegraph/src/litegraph.ts (1)
  • BaseWidget (147-147)
src/lib/litegraph/src/types/widgets.ts (1)
  • IImageCropWidget (266-272)
src/lib/litegraph/src/widgets/BaseWidget.ts (2)
  • DrawWidgetOptions (14-19)
  • WidgetEventOptions (30-34)
src/scripts/widgets.ts (1)
src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts (1)
  • useImageCropWidget (9-26)
src/lib/litegraph/src/widgets/widgetMap.ts (2)
src/lib/litegraph/src/widgets/ImageCropWidget.ts (1)
  • ImageCropWidget (9-47)
src/lib/litegraph/src/utils/type.ts (1)
  • toClass (13-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: deploy-and-comment
  • GitHub Check: test
  • GitHub Check: lint-and-format
  • GitHub Check: setup
  • GitHub Check: collect
🔇 Additional comments (29)
src/locales/en/main.json (1)

1679-1683: LGTM!

The new locale entries follow the established pattern and provide appropriate i18n keys for the image crop widget. Good inclusion of cropPreviewAlt for accessibility.

src/composables/useImageCrop.ts (11)

1-28: LGTM!

Good use of useResizeObserver from VueUse as per coding guidelines. Type imports are properly separated, and the interface is well-defined.


30-66: LGTM!

State management is well-organized with clear separation between image metrics, crop region, drag state, and resize state.


90-108: LGTM!

The image URL retrieval logic correctly uses optional chaining and safely handles missing nodes or URLs by returning null.


110-147: LGTM!

The dimension calculation correctly handles aspect ratio fitting with appropriate guards against division by zero.


149-165: LGTM!

Good handling of CSS transforms by comparing getBoundingClientRect to clientWidth for accurate scale calculation during drag operations.


196-281: LGTM!

Resize handles are well-structured with appropriate cursor classes and positioning. Good use of Math.max(0, ...) to prevent negative dimensions when crop area is small.


283-295: LGTM!

Event handlers correctly manage loading state and error recovery.


297-340: LGTM!

Drag implementation correctly uses pointer capture for reliable tracking and properly clamps coordinates to image bounds.


358-414: LGTM!

The resize logic correctly handles all 8 directions with proper bounds clamping and minimum size enforcement. The left/top edge handling that adjusts both position and size is implemented correctly.


454-477: LGTM!

The composable exposes a clean, well-organized API with all necessary reactive values and event handlers for the component.


174-183: No action needed. The imageUrl is properly constructed using api.apiURL(), which is the recommended approach for backend endpoints per coding guidelines. Image data originates from internal node outputs through URLSearchParams and the trusted API utility, not from user input, making CSS injection a non-issue.

Likely an incorrect or invalid review comment.

src/lib/litegraph/src/widgets/widgetMap.ts (3)

21-21: LGTM!

Import follows alphabetical ordering and existing patterns.


54-54: LGTM!

Type mapping entry follows the established pattern.


125-126: LGTM!

Switch case follows the same pattern as other widget types (e.g., imagecompare, asset).

src/extensions/core/index.ts (1)

13-13: LGTM!

Extension import follows the established alphabetical ordering pattern.

src/schemas/nodeDef/nodeDefSchemaV2.ts (3)

67-72: LGTM!

The schema follows the established pattern (matching zImageCompareInputSpec) and uses valid Zod v3 syntax.


136-136: LGTM!

Union membership correctly placed.


180-180: LGTM!

Type export follows the established pattern.

src/scripts/widgets.ts (2)

17-17: LGTM!

Import follows alphabetical ordering and existing patterns.


299-299: LGTM!

Widget registration follows the established pattern, using transformWidgetConstructorV2ToV1 consistently with other V2 widgets like IMAGECOMPARE.

src/extensions/core/imageCrop.ts (1)

1-11: The extension correctly enforces minimum node dimensions for the ImageCrop widget to ensure adequate space for the crop UI. The pattern matches other core extensions.

The 'ImageCrop' comfyClass name is correct and matches the backend node definition, as confirmed by node definition entries across multiple locales.

src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts (2)

57-59: LGTM!

The async component import follows the established pattern used by other widgets in this registry.


163-171: LGTM!

The widget registration follows the existing convention with appropriate type, alias, and essential: false setting for this specialized widget.

src/lib/litegraph/src/types/widgets.ts (2)

257-272: LGTM!

The CropRegionValue interface and IImageCropWidget type definition follow the established patterns for widget types in this file. The interface properly extends IBaseWidget with appropriate generic parameters.


85-85: LGTM!

The IImageCropWidget is correctly added to the discriminated union type.

src/lib/litegraph/src/widgets/ImageCropWidget.ts (1)

44-46: LGTM!

The no-op onClick is appropriate for a Vue-only widget where interactions are handled by the Vue component.

src/components/imagecrop/WidgetImageCrop.vue (2)

81-101: LGTM!

The crop box and resize handle implementation with pointer event handlers is well-structured. The conditional rendering with v-if and v-show is appropriate for the different elements.


52-79: LGTM!

Good use of i18n for user-facing strings in loading, empty, and alt text states. The image element properly prevents default drag behavior and handles load/error events.

Comment on lines +434 to +450
watch(
() => nodeOutputStore.nodeOutputs,
() => updateImageUrl(),
{ deep: true }
)

watch(
() => nodeOutputStore.nodePreviewImages,
() => updateImageUrl(),
{ deep: true }
)

watch(
() => modelValue.value,
() => syncFromModelValue(),
{ deep: true }
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider optimizing deep watchers on store state.

Deep watching nodeOutputStore.nodeOutputs and nodePreviewImages triggers on any change to these objects, even for unrelated nodes. Consider watching only the specific node's outputs if performance becomes an issue.

🤖 Prompt for AI Agents
In src/composables/useImageCrop.ts around lines 434 to 450, the two deep
watchers are observing the entire store objects nodeOutputStore.nodeOutputs and
nodeOutputStore.nodePreviewImages which causes callbacks to run for any store
change; replace those broad deep watchers with watchers that target only the
current node's outputs/preview (e.g. a computed or getter that returns
nodeOutputStore.nodeOutputs[currentNodeId] and
nodeOutputStore.nodePreviewImages[currentNodeId]) and watch that instead (no
deep:true or only deep if the node's output object needs deep observation). Keep
the modelValue watcher as is; update updateImageUrl calls to receive the
specific node payload from the watcher so only relevant changes trigger the
update.

@Tekaiguy
Copy link

Tekaiguy commented Jan 2, 2026

If possible, I would request adding a ratio input which accepts values like "1.00" or "16:9" to lock the aspect ratio while resizing/dragging. I do a lot of crop work and it is a huge time saver.

The current best custom implementation of visual crop is here, if you want to take a look. Yours is less visually cluttered though, which I like!

@RandomGuyWithIssues
Copy link

Lets say i detect a face in some image and get bbox location, i want to crop that location from the image, do some sampling and return that crop back to the original image. How can i do it with this node if only input and output are image and no other data?

@jtydhr88 jtydhr88 force-pushed the imageCrop-crop-box-new branch from 91fc236 to bc6c542 Compare January 6, 2026 03:10
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI Agents
In @src/components/imagecrop/WidgetImageCrop.vue:
- Around line 81-90: Make the crop box keyboard-accessible: add tabindex="0" and
a focus-visible style to the crop box div (the same element using
:style="cropBoxStyle") and wire an @keydown handler that calls a new
handleKeyboardMove function; implement handleKeyboardMove in the composable that
responds to ArrowUp/Down/Left/Right (preventDefault), computes a small nudge
delta (e.g., 1px or 1 unit, shift for larger step), and updates the same
state/update logic used by handleDragMove/handleDragEnd so keyboard moves behave
identically and remain constrained within image bounds; ensure focus cleanup if
needed and keep naming consistent with
handleDragStart/handleDragMove/handleDragEnd.
- Around line 112-117: The CropRegion type declared in WidgetImageCrop.vue
duplicates the existing CropRegionValue type from
'@/lib/litegraph/src/types/widgets'; instead of a local declaration, import
CropRegionValue and re-export or alias it (e.g., type CropRegion =
CropRegionValue) so there is a single source of truth; update any local
references to use the imported/aliased symbol and remove the duplicate
CropRegion declaration.
- Around line 119-121: Replace the current props object with Vue 3.5 reactive
destructuring: instead of const props = defineProps<{ nodeId: NodeId }>(), call
defineProps with the same type and destructure nodeId directly (i.e., use the
destructured nodeId from defineProps) so the component uses the reactive prop
binding; update all usages that reference props.nodeId to use nodeId.

In @src/composables/useImageCrop.ts:
- Around line 434-444: The deep watchers are monitoring entire stores
(nodeOutputStore.nodeOutputs and nodeOutputStore.nodePreviewImages), causing
unnecessary updates; change the watches to observe only the current node's data
by using a getter that returns nodeOutputStore.nodeOutputs[currentNodeId] and
nodeOutputStore.nodePreviewImages[currentNodeId] (or a computed that derives the
specific output/preview for the active node) and remove deep: true where not
needed, keeping the callback to call updateImageUrl() so only relevant changes
trigger the update.

In @src/extensions/core/imageCrop.ts:
- Around line 1-12: Extract the hard-coded minimum dimensions 300 and 450 into
named constants to improve maintainability: define constants (e.g.,
MIN_IMAGE_CROP_WIDTH and MIN_IMAGE_CROP_HEIGHT) near the top of the module and
replace the literal 300 and 450 in the nodeCreated handler where
node.setSize([Math.max(oldWidth, 300), Math.max(oldHeight, 450)]) is called;
keep the early return and comfyClass check in the async nodeCreated function
unchanged.

In @src/lib/litegraph/src/widgets/ImageCropWidget.ts:
- Around line 15-42: The drawWidget method in ImageCropWidget.ts manually saves
and restores canvas state via destructuring and Object.assign; replace that with
the standard canvas pattern by calling ctx.save() at the start of state changes
in drawWidget (before setting fillStyle, strokeStyle, font, textAlign,
textBaseline) and call ctx.restore() at the end (after drawing the text and
rectangles) to restore all canvas state properties; keep the same drawing logic
and use the existing method name drawWidget and class ImageCropWidget.

In @src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts:
- Around line 9-25: The exported arrow assigned to useImageCropWidget should be
a function declaration per guidelines; change "export const useImageCropWidget =
(): ComfyWidgetConstructorV2 => { ... }" to a named function declaration "export
function useImageCropWidget(): ComfyWidgetConstructorV2 { ... }" and likewise
replace the inner anonymous arrow returned (the (node: LGraphNode, inputSpec:
InputSpecV2) => { ... }) with a named function (e.g., createImageCropWidget) so
both useImageCropWidget and the returned constructor are declared as functions
rather than function expressions; keep the same parameter names, types, body,
and return value IImageCropWidget.

In @src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts:
- Around line 163-171: The registry currently marks 'load3D' as expandable but
not 'imagecrop', causing the WidgetImageCrop component (registered as
'imagecrop' with component WidgetImageCrop) to not get the EXPANDING_TYPES
treatment; update EXPANDING_TYPES to include the 'imagecrop' key so the image
crop widget uses the same expanded/flex layout behavior as Load3D, ensuring the
image preview container can fill available vertical space and provide consistent
interaction area.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91fc236 and bc6c542.

📒 Files selected for processing (12)
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/composables/useImageCrop.ts
  • src/extensions/core/imageCrop.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/types/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/locales/en/main.json
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
🧰 Additional context used
📓 Path-based instructions (19)
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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
src/**/*.{vue,ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
src/lib/litegraph/**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (src/lib/litegraph/CLAUDE.md)

src/lib/litegraph/**/*.{js,ts,jsx,tsx}: Run ESLint instead of manually figuring out whitespace fixes or other trivial style concerns using the pnpm lint:fix command
Take advantage of TypedArray subarray when appropriate
The size and pos properties of Rectangle share the same array buffer (subarray); they may be used to set the rectangle's size and position
Prefer single line if syntax over adding curly braces, when the statement has a very concise expression and concise, single line statement
Do not replace &&= or ||= with = when there is no reason to do so. If you do find a reason to remove either &&= or ||=, leave a comment explaining why the removal occurred
When writing methods, prefer returning idiomatic JavaScript undefined over null

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
src/lib/litegraph/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/lib/litegraph/CLAUDE.md)

Type assertions are an absolute last resort. In almost all cases, they are a crutch that leads to brittle code

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/locales/en/main.json
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
src/**/index.ts

📄 CodeRabbit inference engine (AGENTS.md)

Do not use barrel files (e.g., /some/package/index.ts) to re-export within /src

Files:

  • src/extensions/core/index.ts
src/**/{services,composables}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.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/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.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/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
**/**/use[A-Z]*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name composables using the pattern useXyz.ts

Files:

  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
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/components/imagecrop/WidgetImageCrop.vue
src/components/**/*.vue

📄 CodeRabbit inference engine (src/components/CLAUDE.md)

src/components/**/*.vue: Use setup() function in Vue 3 Composition API
Destructure props using Vue 3.5 style in Vue components
Use ref/reactive for state management in Vue 3 Composition API
Implement computed() for derived state in Vue 3 Composition API
Use provide/inject for dependency injection in Vue components
Prefer emit/@event-name for state changes over other communication patterns
Use defineExpose only for imperative operations (such as form.validate(), modal.open())
Replace PrimeVue Dropdown component with Select
Replace PrimeVue OverlayPanel component with Popover
Replace PrimeVue Calendar component with DatePicker
Replace PrimeVue InputSwitch component with ToggleSwitch
Replace PrimeVue Sidebar component with Drawer
Replace PrimeVue Chips component with AutoComplete with multiple enabled
Replace PrimeVue TabMenu component with Tabs without panels
Replace PrimeVue Steps component with Stepper without panels
Replace PrimeVue InlineMessage component with Message
Extract complex conditionals to computed properties
Implement cleanup for async operations in Vue components
Use lifecycle hooks: onMounted, onUpdated in Vue 3 Composition API
Use Teleport/Suspense when needed for component rendering
Define proper props and emits definitions in Vue components

Files:

  • src/components/imagecrop/WidgetImageCrop.vue
src/components/**/*.{vue,css}

📄 CodeRabbit inference engine (src/components/CLAUDE.md)

src/components/**/*.{vue,css}: Use Tailwind CSS only for styling (no custom CSS)
Use the correct tokens from style.css in the design system package

Files:

  • src/components/imagecrop/WidgetImageCrop.vue
src/components/**/*.{vue,ts,js}

📄 CodeRabbit inference engine (src/components/CLAUDE.md)

src/components/**/*.{vue,ts,js}: Use existing VueUse composables (such as useElementHover) instead of manually managing event listeners
Use useIntersectionObserver for visibility detection instead of custom scroll handlers
Use vue-i18n for ALL UI strings

Files:

  • src/components/imagecrop/WidgetImageCrop.vue
**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • src/components/imagecrop/WidgetImageCrop.vue
🧠 Learnings (31)
📓 Common learnings
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:57.926Z
Learning: In src/renderer/extensions/vueNodes/components/ImagePreview.vue and LGraphNode.vue, keyboard navigation for image galleries should respond to node-level focus (via keyEvent injection from LGraphNode), not require focus within the image preview wrapper itself. This allows users to navigate the gallery with arrow keys immediately when the node is focused/selected.
📚 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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
📚 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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/extensions/core/imageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.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,ts,js} : Use existing VueUse composables (such as useElementHover) instead of manually managing event listeners

Applied to files:

  • src/composables/useImageCrop.ts
  • src/scripts/widgets.ts
📚 Learning: 2025-11-24T19:47:56.371Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/lib/litegraph/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:56.371Z
Learning: Applies to src/lib/litegraph/**/*.{js,ts,jsx,tsx} : Do not replace `&&=` or `||=` with `=` when there is no reason to do so. If you do find a reason to remove either `&&=` or `||=`, leave a comment explaining why the removal occurred

Applied to files:

  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.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} : Use function declarations instead of function expressions when possible

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.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} : Keep functions short and functional; minimize nesting and follow the arrow anti-pattern

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
📚 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/components/imagecrop/WidgetImageCrop.vue
📚 Learning: 2025-12-04T21:43:49.363Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7137
File: src/components/rightSidePanel/parameters/TabParameters.vue:10-0
Timestamp: 2025-12-04T21:43:49.363Z
Learning: Vue 3.5+ supports reactive props destructure in <script setup>. Destructuring props directly (e.g., `const { nodes } = defineProps<{ nodes: LGraphNode[] }>()`) maintains reactivity through compiler transformation. This is the recommended modern approach and does not require using `props.x` or `toRef`/`toRefs`.

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.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 : Destructure props using Vue 3.5 style in Vue components

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.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 Vue 3.5 TypeScript-style default prop declaration with reactive props destructuring; do not use `withDefaults` or runtime props declaration

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.vue
📚 Learning: 2025-11-24T19:47:02.860Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T19:47:02.860Z
Learning: Applies to src/**/*.vue : Use vue 3.5 style of default prop declaration

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.vue
📚 Learning: 2025-11-24T19:47:02.860Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T19:47:02.860Z
Learning: Applies to src/**/*.vue : Utilize ref and reactive for reactive state

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.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 : Use ref/reactive for state management in Vue 3 Composition API

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.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 : Prefer `useModel` over separately defining prop and emit for two-way binding

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.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 : Avoid new usage of PrimeVue components; use VueUse, shadcn/vue, or Reka UI instead

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.vue
  • src/scripts/widgets.ts
📚 Learning: 2025-11-24T19:47:02.860Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T19:47:02.860Z
Learning: Applies to src/**/*.vue : Implement proper props and emits definitions

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.vue
📚 Learning: 2025-12-18T16:03:02.066Z
Learnt from: henrikvilhelmberglund
Repo: Comfy-Org/ComfyUI_frontend PR: 7617
File: src/components/actionbar/ComfyActionbar.vue:301-308
Timestamp: 2025-12-18T16:03:02.066Z
Learning: In the ComfyUI frontend queue system, useQueuePendingTaskCountStore().count indicates the number of tasks in the queue, where count = 1 means a single active/running task and count > 1 means there are pending tasks in addition to the active task. Therefore, in src/components/actionbar/ComfyActionbar.vue, enable the 'Clear Pending Tasks' button only when count > 1 to avoid clearing the currently running task. The active task should be canceled using the 'Cancel current run' button instead. This rule should be enforced via a conditional check on the queue count, with appropriate disabled/aria-disabled states for accessibility, and tests should verify behavior for count = 1 and count > 1.

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.vue
📚 Learning: 2025-12-09T04:35:43.971Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 6300
File: src/locales/en/main.json:774-780
Timestamp: 2025-12-09T04:35:43.971Z
Learning: In the Comfy-Org/ComfyUI_frontend repository, locale files other than `src/locales/en/main.json` are generated automatically on every release. Developers only need to add English (en) key/values in `src/locales/en/main.json` when making PRs; manual updates to other locale files (fr, ja, ko, ru, zh, zh-TW, es, ar, tr, etc.) are not required and should not be suggested in reviews.

Applied to files:

  • src/locales/en/main.json
📚 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/**/{components,composables}/**/*.{ts,tsx,vue} : Use vue-i18n for ALL user-facing strings by adding them to `src/locales/en/main.json`

Applied to files:

  • src/locales/en/main.json
📚 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 : Leverage VueUse functions for performance-enhancing styles in Vue components

Applied to files:

  • src/scripts/widgets.ts
📚 Learning: 2025-11-24T19:47:02.860Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T19:47:02.860Z
Learning: Applies to src/**/*.{vue,ts} : Leverage VueUse functions for performance-enhancing styles

Applied to files:

  • src/scripts/widgets.ts
🧬 Code graph analysis (7)
src/composables/useImageCrop.ts (2)
src/lib/litegraph/src/types/widgets.ts (1)
  • CropRegionValue (258-263)
src/scripts/app.ts (1)
  • app (1772-1772)
src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts (3)
src/scripts/widgets.ts (1)
  • ComfyWidgetConstructorV2 (31-34)
src/lib/litegraph/src/types/widgets.ts (1)
  • IImageCropWidget (266-272)
src/schemas/nodeDef/nodeDefSchemaV2.ts (1)
  • ImageCropInputSpec (180-180)
src/lib/litegraph/src/widgets/widgetMap.ts (2)
src/lib/litegraph/src/widgets/ImageCropWidget.ts (1)
  • ImageCropWidget (9-47)
src/lib/litegraph/src/utils/type.ts (1)
  • toClass (13-18)
src/lib/litegraph/src/widgets/ImageCropWidget.ts (2)
src/lib/litegraph/src/types/widgets.ts (1)
  • IImageCropWidget (266-272)
src/lib/litegraph/src/widgets/BaseWidget.ts (2)
  • DrawWidgetOptions (14-19)
  • WidgetEventOptions (30-34)
src/extensions/core/imageCrop.ts (1)
src/services/extensionService.ts (1)
  • useExtensionService (16-215)
src/schemas/nodeDef/nodeDefSchemaV2.ts (1)
src/schemas/nodeDefSchema.ts (1)
  • zBaseInputOptions (22-37)
src/scripts/widgets.ts (1)
src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts (1)
  • useImageCropWidget (9-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: playwright-tests-chromium-sharded (3, 8)
  • GitHub Check: playwright-tests-chromium-sharded (5, 8)
  • GitHub Check: lint-and-format
  • GitHub Check: test
  • GitHub Check: collect
🔇 Additional comments (13)
src/schemas/nodeDef/nodeDefSchemaV2.ts (1)

67-72: LGTM! Schema definition follows existing patterns.

The new zImageCropInputSpec schema is consistent with similar input specs (IMAGE, IMAGECOMPARE) and properly integrated into the type system. The use of z.record(z.unknown()).optional() for options is Zod v3 compatible and matches the pattern used by other image-related widgets.

Also applies to: 136-136, 180-180

src/extensions/core/index.ts (1)

13-13: LGTM! Import properly added.

The imageCrop extension import is correctly placed in alphabetical order and follows the established pattern for core extensions.

src/lib/litegraph/src/widgets/widgetMap.ts (1)

21-21: LGTM! Widget integration follows established patterns.

The ImageCropWidget is properly integrated into the widget type system:

  • Import is correctly ordered
  • Added to WidgetTypeMap with proper typing
  • Switch case uses the toClass helper consistently with other widgets

Also applies to: 54-54, 125-126

src/scripts/widgets.ts (1)

17-17: LGTM! Widget constructor properly registered.

The IMAGECROP widget is correctly integrated into ComfyWidgets:

  • Import follows alphabetical ordering convention
  • Uses the V2-to-V1 transformation wrapper consistently with other modern widgets
  • Placement in ComfyWidgets object maintains logical grouping with other image-related widgets

Also applies to: 299-299

src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts (1)

57-59: LGTM!

The async component registration follows the established pattern used by other widgets in this file.

src/locales/en/main.json (1)

1688-1692: LGTM!

The locale entries are well-structured and follow the established patterns. All keys are consumed by the WidgetImageCrop.vue component.

src/lib/litegraph/src/widgets/ImageCropWidget.ts (1)

44-46: LGTM!

The no-op onClick with clear documentation that this is a Vue-only widget is appropriate.

src/lib/litegraph/src/types/widgets.ts (2)

85-85: LGTM!

The IImageCropWidget is correctly added to the discriminated union, maintaining consistency with other widget types.


257-272: LGTM!

The CropRegionValue interface and IImageCropWidget are well-defined and follow the established patterns for widget types in this file.

src/composables/useImageCrop.ts (3)

30-31: LGTM!

The composable is well-structured with clear separation of concerns. The onMounted initialization and reactive state management follow Vue 3 Composition API best practices.

Also applies to: 452-452


73-79: Consider using nullish coalescing for explicit intent.

Using || for defaults treats 0 as falsy. While 0 is an invalid crop size (below MIN_CROP_SIZE), using ?? would make the intent clearer that you're defaulting only for undefined/null.

🔎 Proposed change
 const syncFromModelValue = () => {
   const value = modelValue.value
   cropX.value = value.x
   cropY.value = value.y
-  cropWidth.value = value.width || 512
-  cropHeight.value = value.height || 512
+  cropWidth.value = value.width ?? 512
+  cropHeight.value = value.height ?? 512
 }

Likely an incorrect or invalid review comment.


149-165: Potential division by zero not fully guarded.

getEffectiveScale checks for zero values but the check at line 159 happens after rect.width is already used in a potential division context. The guard is correct, but consider returning early before accessing rect.width if clientWidth is falsy.

🔎 Proposed fix
  const getEffectiveScale = (): number => {
    const container = containerEl.value

    if (!container || naturalWidth.value <= 0 || displayedWidth.value <= 0) {
      return 1
    }

    const rect = container.getBoundingClientRect()
    const clientWidth = container.clientWidth

-   if (!clientWidth || !rect.width) return 1
+   if (!clientWidth || rect.width <= 0) return 1

    const renderedDisplayedWidth =
      (displayedWidth.value / clientWidth) * rect.width

    return renderedDisplayedWidth / naturalWidth.value
  }

Likely an incorrect or invalid review comment.

src/components/imagecrop/WidgetImageCrop.vue (1)

11-48: Improve accessibility by associating labels with inputs.

The <label> elements are not programmatically associated with their corresponding inputs. Use id and for attributes for proper accessibility.

🔎 Proposed fix
       <div class="flex flex-1 items-center gap-1">
-        <label class="w-6 text-xs text-muted">X</label>
+        <label for="crop-x" class="w-6 text-xs text-muted">X</label>
         <input
+          id="crop-x"
           v-model.number="cropX"
           type="number"
           :min="0"
           class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
           @change="handleInputChange"
         />
       </div>
       <div class="flex flex-1 items-center gap-1">
-        <label class="w-6 text-xs text-muted">Y</label>
+        <label for="crop-y" class="w-6 text-xs text-muted">Y</label>
         <input
+          id="crop-y"
           v-model.number="cropY"
           ...
       </div>
       <div class="flex flex-1 items-center gap-1">
-        <label class="w-6 text-xs text-muted">W</label>
+        <label for="crop-width" class="w-6 text-xs text-muted">W</label>
         <input
+          id="crop-width"
           v-model.number="cropWidth"
           ...
       </div>
       <div class="flex flex-1 items-center gap-1">
-        <label class="w-6 text-xs text-muted">H</label>
+        <label for="crop-height" class="w-6 text-xs text-muted">H</label>
         <input
+          id="crop-height"
           v-model.number="cropHeight"
           ...

Note: If multiple instances of this widget can exist on the same page, consider using unique IDs by incorporating nodeId.

⛔ Skipped due to learnings
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7603
File: src/components/queue/QueueOverlayHeader.vue:49-59
Timestamp: 2025-12-18T21:15:54.349Z
Learning: For Vue components in Comfy-Org/ComfyUI_frontend: Do not suggest adding aria-label to buttons that already have visible text content (e.g., <span> elements with labels). The visible text automatically provides the accessible name. aria-label should only be used for elements without visible labels, such as icon-only buttons.
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:57.926Z
Learning: In src/renderer/extensions/vueNodes/components/ImagePreview.vue and LGraphNode.vue, keyboard navigation for image galleries should respond to node-level focus (via keyEvent injection from LGraphNode), not require focus within the image preview wrapper itself. This allows users to navigate the gallery with arrow keys immediately when the node is focused/selected.

Comment on lines +1 to +12
import { useExtensionService } from '@/services/extensionService'

useExtensionService().registerExtension({
name: 'Comfy.ImageCrop',

async nodeCreated(node) {
if (node.constructor.comfyClass !== 'ImageCrop') return

const [oldWidth, oldHeight] = node.size
node.setSize([Math.max(oldWidth, 300), Math.max(oldHeight, 450)])
}
})
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

LGTM! Extension registration is correct.

The ImageCrop extension properly:

  • Uses the extension service registration pattern
  • Implements early return for non-ImageCrop nodes
  • Enforces sensible minimum dimensions (300×450) while preserving larger sizes using Math.max

The implementation is clean and follows established patterns for core extensions.

💡 Optional: Consider extracting magic numbers

The minimum dimensions could be extracted as named constants for better maintainability:

+const MIN_IMAGECROP_WIDTH = 300
+const MIN_IMAGECROP_HEIGHT = 450
+
 useExtensionService().registerExtension({
   name: 'Comfy.ImageCrop',
 
   async nodeCreated(node) {
     if (node.constructor.comfyClass !== 'ImageCrop') return
 
     const [oldWidth, oldHeight] = node.size
-    node.setSize([Math.max(oldWidth, 300), Math.max(oldHeight, 450)])
+    node.setSize([Math.max(oldWidth, MIN_IMAGECROP_WIDTH), Math.max(oldHeight, MIN_IMAGECROP_HEIGHT)])
   }
 })
🤖 Prompt for AI Agents
In @src/extensions/core/imageCrop.ts around lines 1-12, Extract the hard-coded
minimum dimensions 300 and 450 into named constants to improve maintainability:
define constants (e.g., MIN_IMAGE_CROP_WIDTH and MIN_IMAGE_CROP_HEIGHT) near the
top of the module and replace the literal 300 and 450 in the nodeCreated handler
where node.setSize([Math.max(oldWidth, 300), Math.max(oldHeight, 450)]) is
called; keep the early return and comfyClass check in the async nodeCreated
function unchanged.

Comment on lines +15 to +42
drawWidget(ctx: CanvasRenderingContext2D, options: DrawWidgetOptions): void {
const { width } = options
const { y, height } = this

const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx

ctx.fillStyle = this.background_color
ctx.fillRect(15, y, width - 30, height)

ctx.strokeStyle = this.outline_color
ctx.strokeRect(15, y, width - 30, height)

ctx.fillStyle = this.text_color
ctx.font = '11px monospace'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'

const text = 'ImageCrop: Vue-only'
ctx.fillText(text, width / 2, y + height / 2)

Object.assign(ctx, {
fillStyle,
strokeStyle,
textAlign,
textBaseline,
font
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Prefer ctx.save()/ctx.restore() for canvas state management.

The manual destructuring and Object.assign approach works but is verbose. The standard canvas idiom is more concise and handles all state properties automatically.

🔎 Proposed refactor
  drawWidget(ctx: CanvasRenderingContext2D, options: DrawWidgetOptions): void {
    const { width } = options
    const { y, height } = this

-   const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx
+   ctx.save()

    ctx.fillStyle = this.background_color
    ctx.fillRect(15, y, width - 30, height)

    ctx.strokeStyle = this.outline_color
    ctx.strokeRect(15, y, width - 30, height)

    ctx.fillStyle = this.text_color
    ctx.font = '11px monospace'
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'

    const text = 'ImageCrop: Vue-only'
    ctx.fillText(text, width / 2, y + height / 2)

-   Object.assign(ctx, {
-     fillStyle,
-     strokeStyle,
-     textAlign,
-     textBaseline,
-     font
-   })
+   ctx.restore()
  }
🤖 Prompt for AI Agents
In @src/lib/litegraph/src/widgets/ImageCropWidget.ts around lines 15-42, The
drawWidget method in ImageCropWidget.ts manually saves and restores canvas state
via destructuring and Object.assign; replace that with the standard canvas
pattern by calling ctx.save() at the start of state changes in drawWidget
(before setting fillStyle, strokeStyle, font, textAlign, textBaseline) and call
ctx.restore() at the end (after drawing the text and rectangles) to restore all
canvas state properties; keep the same drawing logic and use the existing method
name drawWidget and class ImageCropWidget.

Comment on lines +9 to +25
export const useImageCropWidget = (): ComfyWidgetConstructorV2 => {
return (node: LGraphNode, inputSpec: InputSpecV2): IImageCropWidget => {
const { name, options = {} } = inputSpec as ImageCropInputSpec

const widget = node.addWidget(
'imagecrop',
name,
{ x: 0, y: 0, width: 512, height: 512 },
() => {},
{
serialize: true,
...options
}
) as IImageCropWidget

return widget
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider using a function declaration per coding guidelines.

Per repository guidelines, prefer pure function declarations over function expressions for better hoisting clarity and consistency.

🔎 Proposed refactor
-export const useImageCropWidget = (): ComfyWidgetConstructorV2 => {
-  return (node: LGraphNode, inputSpec: InputSpecV2): IImageCropWidget => {
+export function useImageCropWidget(): ComfyWidgetConstructorV2 {
+  return function (node: LGraphNode, inputSpec: InputSpecV2): IImageCropWidget {
     const { name, options = {} } = inputSpec as ImageCropInputSpec

     const widget = node.addWidget(
       'imagecrop',
       name,
       { x: 0, y: 0, width: 512, height: 512 },
       () => {},
       {
         serialize: true,
         ...options
       }
     ) as IImageCropWidget

     return widget
   }
 }

Based on learnings: Prefer pure function declarations over function expressions.

📝 Committable suggestion

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

Suggested change
export const useImageCropWidget = (): ComfyWidgetConstructorV2 => {
return (node: LGraphNode, inputSpec: InputSpecV2): IImageCropWidget => {
const { name, options = {} } = inputSpec as ImageCropInputSpec
const widget = node.addWidget(
'imagecrop',
name,
{ x: 0, y: 0, width: 512, height: 512 },
() => {},
{
serialize: true,
...options
}
) as IImageCropWidget
return widget
}
export function useImageCropWidget(): ComfyWidgetConstructorV2 {
return function (node: LGraphNode, inputSpec: InputSpecV2): IImageCropWidget {
const { name, options = {} } = inputSpec as ImageCropInputSpec
const widget = node.addWidget(
'imagecrop',
name,
{ x: 0, y: 0, width: 512, height: 512 },
() => {},
{
serialize: true,
...options
}
) as IImageCropWidget
return widget
}
}
🤖 Prompt for AI Agents
In @src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
around lines 9-25, The exported arrow assigned to useImageCropWidget should be a
function declaration per guidelines; change "export const useImageCropWidget =
(): ComfyWidgetConstructorV2 => { ... }" to a named function declaration "export
function useImageCropWidget(): ComfyWidgetConstructorV2 { ... }" and likewise
replace the inner anonymous arrow returned (the (node: LGraphNode, inputSpec:
InputSpecV2) => { ... }) with a named function (e.g., createImageCropWidget) so
both useImageCropWidget and the returned constructor are declared as functions
rather than function expressions; keep the same parameter names, types, body,
and return value IImageCropWidget.

@jtydhr88 jtydhr88 force-pushed the imageCrop-crop-box-new branch from bc6c542 to 567a771 Compare January 6, 2026 03:20
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 Fix all issues with AI Agents
In @src/components/imagecrop/WidgetImageCrop.vue:
- Line 96: The Vue template uses array class binding :class="['absolute',
handle.class]" in WidgetImageCrop.vue; replace this with the cn() utility from
@/utils/tailwindUtil: import cn in the script (or setup) and change the binding
to use :class="cn('absolute', handle.class)" so Tailwind class merging follows
project conventions; ensure the import name is cn and update any other similar
bindings in this component to use cn() for consistency.
- Around line 113-115: The current use of defineProps should be converted to Vue
3.5 reactive props destructuring: keep the typed defineProps call (defineProps<{
nodeId: NodeId }>()), then call toRefs on the returned props and extract nodeId
(e.g., const { nodeId } = toRefs(props)), and update usages to read nodeId.value
where needed; change references to the original props object to use the
destructured ref instead.

In @src/composables/useImageCrop.ts:
- Around line 73-79: In syncFromModelValue replace the `||` fallbacks with
nullish coalescing so defaults only apply for null/undefined: read
modelValue.value and set cropWidth.value = value.width ?? 512 and
cropHeight.value = value.height ?? 512 (leave cropX/cropY assignment as-is);
this clarifies intent that 0 is a deliberate value rather than treated as
missing.
- Around line 434-444: The current deep watchers on nodeOutputStore.nodeOutputs
and nodeOutputStore.nodePreviewImages fire on any mutation across all nodes;
change them to watch only the specific node's outputs/preview for the node being
edited (e.g., replace watch(() => nodeOutputStore.nodeOutputs, ...) with
watch(() => nodeOutputStore.nodeOutputs[nodeId] or a store getter like
nodeOutputStore.getNodeOutputs(nodeId), and similarly for nodePreviewImages) and
remove deep: true unless nested mutation detection for that specific node is
required; call updateImageUrl() in those targeted watchers so unrelated node
changes no longer trigger updates.

In @src/extensions/core/imageCrop.ts:
- Around line 9-10: Extract the magic numbers used when constraining image crop
sizes into named constants to improve readability and maintainability: define
MIN_IMAGECROP_WIDTH and MIN_IMAGECROP_HEIGHT near the top of the module and
replace the literal 300 and 450 in the node.setSize call (the code that reads
node.size and calls node.setSize([Math.max(oldWidth, 300), Math.max(oldHeight,
450)]) ) with those constants so the intent is clear and easy to update.

In @src/lib/litegraph/src/types/widgets.ts:
- Around line 257-263: CropRegionValue currently only enforces numeric types at
compile time; add runtime validation in the ImageCrop widget implementation to
ensure CropRegionValue.width and CropRegionValue.height are positive (>0) and
x/y are within expected bounds (e.g., non-negative and not outside image
dimensions if available). Validate inputs when constructing/updating the crop
region (e.g., in the widget's setValue/updateValue/handleChange functions) and
reject or normalize invalid values (throw error or clamp to valid range and log)
so downstream code always receives a valid CropRegionValue.

In @src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts:
- Line 22: The code uses a type assertion "as IImageCropWidget" on the result of
node.addWidget; remove the assertion by fixing typings so the call infers the
correct widget type — either call node.addWidget with a generic parameter (e.g.,
node.addWidget<IImageCropWidget>(...)) or update the widget API signature so
addWidget's return type is strongly typed for this widget. If changing the API
isn't possible now, keep the assertion but add a brief comment above it
explaining why the assertion is unavoidable and reference the related typing
limitation (IImageCropWidget / node.addWidget) so future refactors can remove
it.

In @src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts:
- Around line 163-171: The EXPANDING_TYPES set should include the 'imagecrop'
widget so it can expand vertically like 'load3D'; update the EXPANDING_TYPES
declaration (referencing the EXPANDING_TYPES symbol in widgetRegistry.ts) to add
the 'imagecrop' key (matching the widget id used in the registry entry for
component WidgetImageCrop) so the image crop preview can use available vertical
space and improve UX.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc6c542 and 567a771.

📒 Files selected for processing (12)
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/composables/useImageCrop.ts
  • src/extensions/core/imageCrop.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/types/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/locales/en/main.json
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/widgets.ts
🧰 Additional context used
📓 Path-based instructions (19)
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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/extensions/core/imageCrop.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/extensions/core/imageCrop.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/extensions/core/imageCrop.ts
src/**/*.{vue,ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/extensions/core/imageCrop.ts
src/lib/litegraph/**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (src/lib/litegraph/CLAUDE.md)

src/lib/litegraph/**/*.{js,ts,jsx,tsx}: Run ESLint instead of manually figuring out whitespace fixes or other trivial style concerns using the pnpm lint:fix command
Take advantage of TypedArray subarray when appropriate
The size and pos properties of Rectangle share the same array buffer (subarray); they may be used to set the rectangle's size and position
Prefer single line if syntax over adding curly braces, when the statement has a very concise expression and concise, single line statement
Do not replace &&= or ||= with = when there is no reason to do so. If you do find a reason to remove either &&= or ||=, leave a comment explaining why the removal occurred
When writing methods, prefer returning idiomatic JavaScript undefined over null

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
src/lib/litegraph/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/lib/litegraph/CLAUDE.md)

Type assertions are an absolute last resort. In almost all cases, they are a crutch that leads to brittle code

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/extensions/core/imageCrop.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/locales/en/main.json
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/extensions/core/imageCrop.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • src/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/extensions/core/imageCrop.ts
src/**/index.ts

📄 CodeRabbit inference engine (AGENTS.md)

Do not use barrel files (e.g., /some/package/index.ts) to re-export within /src

Files:

  • src/extensions/core/index.ts
src/**/{services,composables}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.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/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.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/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
**/**/use[A-Z]*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name composables using the pattern useXyz.ts

Files:

  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
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/components/imagecrop/WidgetImageCrop.vue
src/components/**/*.vue

📄 CodeRabbit inference engine (src/components/CLAUDE.md)

src/components/**/*.vue: Use setup() function in Vue 3 Composition API
Destructure props using Vue 3.5 style in Vue components
Use ref/reactive for state management in Vue 3 Composition API
Implement computed() for derived state in Vue 3 Composition API
Use provide/inject for dependency injection in Vue components
Prefer emit/@event-name for state changes over other communication patterns
Use defineExpose only for imperative operations (such as form.validate(), modal.open())
Replace PrimeVue Dropdown component with Select
Replace PrimeVue OverlayPanel component with Popover
Replace PrimeVue Calendar component with DatePicker
Replace PrimeVue InputSwitch component with ToggleSwitch
Replace PrimeVue Sidebar component with Drawer
Replace PrimeVue Chips component with AutoComplete with multiple enabled
Replace PrimeVue TabMenu component with Tabs without panels
Replace PrimeVue Steps component with Stepper without panels
Replace PrimeVue InlineMessage component with Message
Extract complex conditionals to computed properties
Implement cleanup for async operations in Vue components
Use lifecycle hooks: onMounted, onUpdated in Vue 3 Composition API
Use Teleport/Suspense when needed for component rendering
Define proper props and emits definitions in Vue components

Files:

  • src/components/imagecrop/WidgetImageCrop.vue
src/components/**/*.{vue,css}

📄 CodeRabbit inference engine (src/components/CLAUDE.md)

src/components/**/*.{vue,css}: Use Tailwind CSS only for styling (no custom CSS)
Use the correct tokens from style.css in the design system package

Files:

  • src/components/imagecrop/WidgetImageCrop.vue
src/components/**/*.{vue,ts,js}

📄 CodeRabbit inference engine (src/components/CLAUDE.md)

src/components/**/*.{vue,ts,js}: Use existing VueUse composables (such as useElementHover) instead of manually managing event listeners
Use useIntersectionObserver for visibility detection instead of custom scroll handlers
Use vue-i18n for ALL UI strings

Files:

  • src/components/imagecrop/WidgetImageCrop.vue
**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • src/components/imagecrop/WidgetImageCrop.vue
🧠 Learnings (27)
📚 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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/extensions/core/imageCrop.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/extensions/core/imageCrop.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/extensions/core/imageCrop.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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/extensions/core/imageCrop.ts
📚 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/lib/litegraph/src/types/widgets.ts
  • src/extensions/core/index.ts
  • src/lib/litegraph/src/widgets/widgetMap.ts
  • src/composables/useImageCrop.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
  • src/scripts/widgets.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
  • src/components/imagecrop/WidgetImageCrop.vue
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/extensions/core/imageCrop.ts
📚 Learning: 2025-12-09T04:35:43.971Z
Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 6300
File: src/locales/en/main.json:774-780
Timestamp: 2025-12-09T04:35:43.971Z
Learning: In the Comfy-Org/ComfyUI_frontend repository, locale files other than `src/locales/en/main.json` are generated automatically on every release. Developers only need to add English (en) key/values in `src/locales/en/main.json` when making PRs; manual updates to other locale files (fr, ja, ko, ru, zh, zh-TW, es, ar, tr, etc.) are not required and should not be suggested in reviews.

Applied to files:

  • src/locales/en/main.json
📚 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,ts,js} : Use existing VueUse composables (such as useElementHover) instead of manually managing event listeners

Applied to files:

  • src/composables/useImageCrop.ts
  • src/scripts/widgets.ts
📚 Learning: 2025-11-24T19:47:56.371Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/lib/litegraph/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:56.371Z
Learning: Applies to src/lib/litegraph/**/*.{js,ts,jsx,tsx} : Do not replace `&&=` or `||=` with `=` when there is no reason to do so. If you do find a reason to remove either `&&=` or `||=`, leave a comment explaining why the removal occurred

Applied to files:

  • src/composables/useImageCrop.ts
  • src/lib/litegraph/src/widgets/ImageCropWidget.ts
📚 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/**/{composables,components}/**/*.{ts,tsx,vue} : Clean up subscriptions in state management to prevent memory leaks

Applied to files:

  • src/composables/useImageCrop.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} : Use function declarations instead of function expressions when possible

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.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} : Keep functions short and functional; minimize nesting and follow the arrow anti-pattern

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.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 **/*.vue : Leverage VueUse functions for performance-enhancing styles in Vue components

Applied to files:

  • src/scripts/widgets.ts
📚 Learning: 2025-11-24T19:47:02.860Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T19:47:02.860Z
Learning: Applies to src/**/*.{vue,ts} : Leverage VueUse functions for performance-enhancing styles

Applied to files:

  • src/scripts/widgets.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 **/*.vue : Avoid new usage of PrimeVue components; use VueUse, shadcn/vue, or Reka UI instead

Applied to files:

  • src/scripts/widgets.ts
📚 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/components/imagecrop/WidgetImageCrop.vue
📚 Learning: 2025-12-11T03:55:57.926Z
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:57.926Z
Learning: In src/renderer/extensions/vueNodes/components/ImagePreview.vue and LGraphNode.vue, keyboard navigation for image galleries should respond to node-level focus (via keyEvent injection from LGraphNode), not require focus within the image preview wrapper itself. This allows users to navigate the gallery with arrow keys immediately when the node is focused/selected.

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.vue
📚 Learning: 2025-12-04T21:43:49.363Z
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7137
File: src/components/rightSidePanel/parameters/TabParameters.vue:10-0
Timestamp: 2025-12-04T21:43:49.363Z
Learning: Vue 3.5+ supports reactive props destructure in <script setup>. Destructuring props directly (e.g., `const { nodes } = defineProps<{ nodes: LGraphNode[] }>()`) maintains reactivity through compiler transformation. This is the recommended modern approach and does not require using `props.x` or `toRef`/`toRefs`.

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.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 : Destructure props using Vue 3.5 style in Vue components

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.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 Vue 3.5 TypeScript-style default prop declaration with reactive props destructuring; do not use `withDefaults` or runtime props declaration

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.vue
📚 Learning: 2025-11-24T19:47:02.860Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T19:47:02.860Z
Learning: Applies to src/**/*.vue : Use vue 3.5 style of default prop declaration

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.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/components/imagecrop/WidgetImageCrop.vue
📚 Learning: 2025-12-18T16:03:02.066Z
Learnt from: henrikvilhelmberglund
Repo: Comfy-Org/ComfyUI_frontend PR: 7617
File: src/components/actionbar/ComfyActionbar.vue:301-308
Timestamp: 2025-12-18T16:03:02.066Z
Learning: In the ComfyUI frontend queue system, useQueuePendingTaskCountStore().count indicates the number of tasks in the queue, where count = 1 means a single active/running task and count > 1 means there are pending tasks in addition to the active task. Therefore, in src/components/actionbar/ComfyActionbar.vue, enable the 'Clear Pending Tasks' button only when count > 1 to avoid clearing the currently running task. The active task should be canceled using the 'Cancel current run' button instead. This rule should be enforced via a conditional check on the queue count, with appropriate disabled/aria-disabled states for accessibility, and tests should verify behavior for count = 1 and count > 1.

Applied to files:

  • src/components/imagecrop/WidgetImageCrop.vue
🧬 Code graph analysis (5)
src/lib/litegraph/src/widgets/widgetMap.ts (2)
src/lib/litegraph/src/widgets/ImageCropWidget.ts (1)
  • ImageCropWidget (9-47)
src/lib/litegraph/src/utils/type.ts (1)
  • toClass (13-18)
src/composables/useImageCrop.ts (2)
src/lib/litegraph/src/types/widgets.ts (1)
  • CropRegionValue (258-263)
src/scripts/app.ts (1)
  • app (1772-1772)
src/scripts/widgets.ts (1)
src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts (1)
  • useImageCropWidget (9-26)
src/lib/litegraph/src/widgets/ImageCropWidget.ts (2)
src/lib/litegraph/src/types/widgets.ts (1)
  • IImageCropWidget (266-272)
src/lib/litegraph/src/widgets/BaseWidget.ts (2)
  • DrawWidgetOptions (14-19)
  • WidgetEventOptions (30-34)
src/schemas/nodeDef/nodeDefSchemaV2.ts (1)
src/schemas/nodeDefSchema.ts (1)
  • zBaseInputOptions (22-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test
  • GitHub Check: collect
  • GitHub Check: lint-and-format
🔇 Additional comments (10)
src/extensions/core/index.ts (1)

13-13: LGTM! Extension import is correct.

The imageCrop extension is properly imported and follows the established pattern of side-effect imports for core extensions.

src/locales/en/main.json (1)

1688-1692: LGTM! Locale additions are appropriate.

The new imageCrop locale object provides clear, user-facing text for the image cropping widget UI. The strings cover loading state, missing input, and accessibility text for the crop preview.

src/lib/litegraph/src/widgets/widgetMap.ts (1)

21-21: LGTM! ImageCropWidget integration is consistent.

The ImageCropWidget is properly integrated into the widget mapping system:

  • Import follows alphabetical ordering
  • Type mapping added to WidgetTypeMap
  • Switch case uses the established toClass pattern

The implementation is clean and follows existing conventions.

Also applies to: 54-54, 125-126

src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts (1)

57-59: LGTM! Widget registration is correctly implemented.

The WidgetImageCrop component is properly registered:

  • Async component definition follows the established pattern
  • Widget definition includes appropriate component, aliases, and essential flag
  • Structure is consistent with other widget registrations like Load3D

Also applies to: 163-171

src/scripts/widgets.ts (1)

17-17: LGTM!

The import and widget registration follow the established pattern used by other widgets in the system. The integration is consistent and correct.

Also applies to: 299-299

src/lib/litegraph/src/types/widgets.ts (1)

257-272: LGTM! Type definitions are well-structured.

The CropRegionValue interface and IImageCropWidget type follow the established widget pattern and are properly integrated into the type system.

src/schemas/nodeDef/nodeDefSchemaV2.ts (1)

67-72: LGTM! Schema definition follows established patterns.

The zImageCropInputSpec schema is consistent with other input specifications (like zImageCompareInputSpec) and properly integrated into the union type and exports.

Also applies to: 136-136, 180-180

src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts (1)

9-26: Consider using function declarations per coding guidelines.

Per repository guidelines, prefer pure function declarations over function expressions for better hoisting clarity and consistency.

🔎 Proposed refactor
-export const useImageCropWidget = (): ComfyWidgetConstructorV2 => {
-  return (node: LGraphNode, inputSpec: InputSpecV2): IImageCropWidget => {
+export function useImageCropWidget(): ComfyWidgetConstructorV2 {
+  return function (node: LGraphNode, inputSpec: InputSpecV2): IImageCropWidget {
     const { name, options = {} } = inputSpec as ImageCropInputSpec

     const widget = node.addWidget(
       'imagecrop',
       name,
       { x: 0, y: 0, width: 512, height: 512 },
       () => {},
       {
         serialize: true,
         ...options
       }
     ) as IImageCropWidget

     return widget
   }
 }

Based on learnings: Prefer pure function declarations over function expressions.

⛔ Skipped due to learnings
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:17.361Z
Learning: In the Comfy-Org/ComfyUI_frontend repository, prefer pure function declarations (e.g., `function foo() { ... }`) over function expressions (e.g., `const foo = () => { ... }`) for pure functions, as function declarations are considered more functional than function expressions.
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} : Use function declarations instead of function expressions when possible
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} : Keep functions short and functional; minimize nesting and follow the arrow anti-pattern
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/**/*.{vue,ts,tsx} : Follow Vue 3 composition API style guide
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} : Write code that is expressive and self-documenting; avoid redundant comments and clean as you go
src/lib/litegraph/src/widgets/ImageCropWidget.ts (1)

15-42: Prefer ctx.save()/ctx.restore() for canvas state management.

The manual destructuring and Object.assign approach works but is verbose. The standard canvas idiom is more concise and handles all state properties automatically.

🔎 Proposed refactor
  drawWidget(ctx: CanvasRenderingContext2D, options: DrawWidgetOptions): void {
    const { width } = options
    const { y, height } = this

-   const { fillStyle, strokeStyle, textAlign, textBaseline, font } = ctx
+   ctx.save()

    ctx.fillStyle = this.background_color
    ctx.fillRect(15, y, width - 30, height)

    ctx.strokeStyle = this.outline_color
    ctx.strokeRect(15, y, width - 30, height)

    ctx.fillStyle = this.text_color
    ctx.font = '11px monospace'
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'

    const text = 'ImageCrop: Vue-only'
    ctx.fillText(text, width / 2, y + height / 2)

-   Object.assign(ctx, {
-     fillStyle,
-     strokeStyle,
-     textAlign,
-     textBaseline,
-     font
-   })
+   ctx.restore()
  }

Likely an incorrect or invalid review comment.

src/components/imagecrop/WidgetImageCrop.vue (1)

81-90: Consider adding keyboard accessibility for the crop box.

The crop box only responds to pointer events. Consider adding keyboard support (arrow keys for fine-grained movement) for accessibility, especially since users may want precise control.

🔎 Proposed enhancement

Add tabindex="0" to make the crop box focusable, then handle @keydown events for arrow key navigation:

<div
  v-if="imageUrl && !isLoading"
  tabindex="0"
  class="absolute box-border cursor-move overflow-hidden border-2 border-white focus:ring-2 focus:ring-primary"
  :style="cropBoxStyle"
  @pointerdown="handleDragStart"
  @pointermove="handleDragMove"
  @pointerup="handleDragEnd"
  @keydown="handleKeyboardMove"
>

This would require adding a handleKeyboardMove function in the composable to support arrow key movement.

⛔ Skipped due to learnings
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:57.926Z
Learning: In src/renderer/extensions/vueNodes/components/ImagePreview.vue and LGraphNode.vue, keyboard navigation for image galleries should respond to node-level focus (via keyEvent injection from LGraphNode), not require focus within the image preview wrapper itself. This allows users to navigate the gallery with arrow keys immediately when the node is focused/selected.

Comment on lines +11 to +48
<label class="w-6 text-xs text-muted">X</label>
<input
v-model.number="cropX"
type="number"
:min="0"
class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
@change="handleInputChange"
/>
</div>
<div class="flex flex-1 items-center gap-1">
<label class="w-6 text-xs text-muted">Y</label>
<input
v-model.number="cropY"
type="number"
:min="0"
class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
@change="handleInputChange"
/>
</div>
<div class="flex flex-1 items-center gap-1">
<label class="w-6 text-xs text-muted">W</label>
<input
v-model.number="cropWidth"
type="number"
:min="1"
class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
@change="handleInputChange"
/>
</div>
<div class="flex flex-1 items-center gap-1">
<label class="w-6 text-xs text-muted">H</label>
<input
v-model.number="cropHeight"
type="number"
:min="1"
class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
@change="handleInputChange"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Improve accessibility by associating labels with inputs.

The <label> elements are not programmatically associated with their corresponding inputs. Use id and for attributes or wrap inputs in labels for proper accessibility.

🔎 Proposed fix for one input (apply pattern to all)
       <div class="flex flex-1 items-center gap-1">
-        <label class="w-6 text-xs text-muted">X</label>
+        <label for="crop-x" class="w-6 text-xs text-muted">X</label>
         <input
+          id="crop-x"
           v-model.number="cropX"
           type="number"
           :min="0"
           class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
           @change="handleInputChange"
         />
       </div>

Apply the same pattern for Y (id="crop-y"), W (id="crop-width"), and H (id="crop-height").

📝 Committable suggestion

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

Suggested change
<label class="w-6 text-xs text-muted">X</label>
<input
v-model.number="cropX"
type="number"
:min="0"
class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
@change="handleInputChange"
/>
</div>
<div class="flex flex-1 items-center gap-1">
<label class="w-6 text-xs text-muted">Y</label>
<input
v-model.number="cropY"
type="number"
:min="0"
class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
@change="handleInputChange"
/>
</div>
<div class="flex flex-1 items-center gap-1">
<label class="w-6 text-xs text-muted">W</label>
<input
v-model.number="cropWidth"
type="number"
:min="1"
class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
@change="handleInputChange"
/>
</div>
<div class="flex flex-1 items-center gap-1">
<label class="w-6 text-xs text-muted">H</label>
<input
v-model.number="cropHeight"
type="number"
:min="1"
class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
@change="handleInputChange"
/>
<label for="crop-x" class="w-6 text-xs text-muted">X</label>
<input
id="crop-x"
v-model.number="cropX"
type="number"
:min="0"
class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
@change="handleInputChange"
/>
</div>
<div class="flex flex-1 items-center gap-1">
<label for="crop-y" class="w-6 text-xs text-muted">Y</label>
<input
id="crop-y"
v-model.number="cropY"
type="number"
:min="0"
class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
@change="handleInputChange"
/>
</div>
<div class="flex flex-1 items-center gap-1">
<label for="crop-width" class="w-6 text-xs text-muted">W</label>
<input
id="crop-width"
v-model.number="cropWidth"
type="number"
:min="1"
class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
@change="handleInputChange"
/>
</div>
<div class="flex flex-1 items-center gap-1">
<label for="crop-height" class="w-6 text-xs text-muted">H</label>
<input
id="crop-height"
v-model.number="cropHeight"
type="number"
:min="1"
class="h-6 w-full rounded border border-border bg-input px-1 text-xs"
@change="handleInputChange"
/>

v-for="handle in resizeHandles"
v-show="imageUrl && !isLoading"
:key="handle.direction"
:class="['absolute', handle.class]"
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Use cn() utility instead of array class syntax.

Per coding guidelines, use the cn() utility function from @/utils/tailwindUtil for merging Tailwind class names instead of :class="[]" syntax.

🔎 Proposed refactor

Import the utility at the top of the script:

+import { cn } from '@/utils/tailwindUtil'

Then update the binding:

-        :class="['absolute', handle.class]"
+        :class="cn('absolute', handle.class)"

Based on coding guidelines.

📝 Committable suggestion

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

Suggested change
:class="['absolute', handle.class]"
import { cn } from '@/utils/tailwindUtil'
:class="cn('absolute', handle.class)"
🤖 Prompt for AI Agents
In @src/components/imagecrop/WidgetImageCrop.vue around line 96, The Vue
template uses array class binding :class="['absolute', handle.class]" in
WidgetImageCrop.vue; replace this with the cn() utility from
@/utils/tailwindUtil: import cn in the script (or setup) and change the binding
to use :class="cn('absolute', handle.class)" so Tailwind class merging follows
project conventions; ensure the import name is cn and update any other similar
bindings in this component to use cn() for consistency.

Comment on lines +113 to +115
const props = defineProps<{
nodeId: NodeId
}>()
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Use Vue 3.5 style props destructuring.

Per coding guidelines, prefer reactive props destructuring in Vue 3.5+.

🔎 Proposed refactor
-const props = defineProps<{
+const { nodeId } = defineProps<{
   nodeId: NodeId
 }>()

 ...

-} = useImageCrop(props.nodeId, { imageEl, containerEl, modelValue })
+} = useImageCrop(nodeId, { imageEl, containerEl, modelValue })

Based on learnings and coding guidelines.

📝 Committable suggestion

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

Suggested change
const props = defineProps<{
nodeId: NodeId
}>()
const { nodeId } = defineProps<{
nodeId: NodeId
}>()
🤖 Prompt for AI Agents
In @src/components/imagecrop/WidgetImageCrop.vue around lines 113-115, The
current use of defineProps should be converted to Vue 3.5 reactive props
destructuring: keep the typed defineProps call (defineProps<{ nodeId: NodeId
}>()), then call toRefs on the returned props and extract nodeId (e.g., const {
nodeId } = toRefs(props)), and update usages to read nodeId.value where needed;
change references to the original props object to use the destructured ref
instead.

Comment on lines +73 to +79
const syncFromModelValue = () => {
const value = modelValue.value
cropX.value = value.x
cropY.value = value.y
cropWidth.value = value.width || 512
cropHeight.value = value.height || 512
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider using nullish coalescing for explicit intent.

Using || for defaults treats 0 as falsy. While 0 is an invalid crop size (below MIN_CROP_SIZE), using ?? would make the intent clearer that you're defaulting only for undefined/null.

🔎 Proposed change
 const syncFromModelValue = () => {
   const value = modelValue.value
   cropX.value = value.x
   cropY.value = value.y
-  cropWidth.value = value.width || 512
-  cropHeight.value = value.height || 512
+  cropWidth.value = value.width ?? 512
+  cropHeight.value = value.height ?? 512
 }
🤖 Prompt for AI Agents
In @src/composables/useImageCrop.ts around lines 73-79, In syncFromModelValue
replace the `||` fallbacks with nullish coalescing so defaults only apply for
null/undefined: read modelValue.value and set cropWidth.value = value.width ??
512 and cropHeight.value = value.height ?? 512 (leave cropX/cropY assignment
as-is); this clarifies intent that 0 is a deliberate value rather than treated
as missing.

Comment on lines +434 to +444
watch(
() => nodeOutputStore.nodeOutputs,
() => updateImageUrl(),
{ deep: true }
)

watch(
() => nodeOutputStore.nodePreviewImages,
() => updateImageUrl(),
{ deep: true }
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider optimizing deep watchers on store state.

Deep watching nodeOutputStore.nodeOutputs and nodePreviewImages triggers on any change to these objects, even for unrelated nodes. Consider watching only the specific node's outputs if performance becomes an issue with many nodes.

🔎 Proposed optimization
+ const nodeOutputs = computed(() => {
+   if (!node.value) return null
+   return nodeOutputStore.nodeOutputs[node.value.id]
+ })
+
+ const nodePreviewImages = computed(() => {
+   if (!node.value) return null
+   const inputNode = node.value.getInputNode(0)
+   return inputNode ? nodeOutputStore.nodePreviewImages[inputNode.id] : null
+ })

  watch(
-   () => nodeOutputStore.nodeOutputs,
+   nodeOutputs,
    () => updateImageUrl(),
-   { deep: true }
  )

  watch(
-   () => nodeOutputStore.nodePreviewImages,
+   nodePreviewImages,
    () => updateImageUrl(),
-   { deep: true }
  )

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

🤖 Prompt for AI Agents
In @src/composables/useImageCrop.ts around lines 434-444, The current deep
watchers on nodeOutputStore.nodeOutputs and nodeOutputStore.nodePreviewImages
fire on any mutation across all nodes; change them to watch only the specific
node's outputs/preview for the node being edited (e.g., replace watch(() =>
nodeOutputStore.nodeOutputs, ...) with watch(() =>
nodeOutputStore.nodeOutputs[nodeId] or a store getter like
nodeOutputStore.getNodeOutputs(nodeId), and similarly for nodePreviewImages) and
remove deep: true unless nested mutation detection for that specific node is
required; call updateImageUrl() in those targeted watchers so unrelated node
changes no longer trigger updates.

Comment on lines +9 to +10
const [oldWidth, oldHeight] = node.size
node.setSize([Math.max(oldWidth, 300), Math.max(oldHeight, 450)])
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider extracting magic numbers for maintainability.

The minimum dimensions 300 and 450 are hardcoded. Extracting them as named constants (e.g., MIN_IMAGECROP_WIDTH and MIN_IMAGECROP_HEIGHT) would improve code maintainability and readability.

🔎 Proposed refactor
+const MIN_IMAGECROP_WIDTH = 300
+const MIN_IMAGECROP_HEIGHT = 450
+
 useExtensionService().registerExtension({
   name: 'Comfy.ImageCrop',
 
   async nodeCreated(node) {
     if (node.constructor.comfyClass !== 'ImageCrop') return
 
     const [oldWidth, oldHeight] = node.size
-    node.setSize([Math.max(oldWidth, 300), Math.max(oldHeight, 450)])
+    node.setSize([Math.max(oldWidth, MIN_IMAGECROP_WIDTH), Math.max(oldHeight, MIN_IMAGECROP_HEIGHT)])
   }
 })
🤖 Prompt for AI Agents
In @src/extensions/core/imageCrop.ts around lines 9-10, Extract the magic
numbers used when constraining image crop sizes into named constants to improve
readability and maintainability: define MIN_IMAGECROP_WIDTH and
MIN_IMAGECROP_HEIGHT near the top of the module and replace the literal 300 and
450 in the node.setSize call (the code that reads node.size and calls
node.setSize([Math.max(oldWidth, 300), Math.max(oldHeight, 450)]) ) with those
constants so the intent is clear and easy to update.

Comment on lines +257 to +263
/** Crop region value for ImageCrop widget */
export interface CropRegionValue {
x: number
y: number
width: number
height: number
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding runtime validation for crop region values.

While TypeScript enforces the numeric types at compile time, there's no validation for logical constraints (e.g., negative dimensions, width/height of zero). Consider adding runtime validation in the widget implementation to ensure width and height are positive and coordinates are valid.

🤖 Prompt for AI Agents
In @src/lib/litegraph/src/types/widgets.ts around lines 257-263, CropRegionValue
currently only enforces numeric types at compile time; add runtime validation in
the ImageCrop widget implementation to ensure CropRegionValue.width and
CropRegionValue.height are positive (>0) and x/y are within expected bounds
(e.g., non-negative and not outside image dimensions if available). Validate
inputs when constructing/updating the crop region (e.g., in the widget's
setValue/updateValue/handleChange functions) and reject or normalize invalid
values (throw error or clamp to valid range and log) so downstream code always
receives a valid CropRegionValue.

const widget = node.addWidget(
'imagecrop',
name,
{ x: 0, y: 0, width: 512, height: 512 },
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's read the full composable file to understand the context
cat -n src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1013


🏁 Script executed:

# Search for validation logic in image crop related files as suggested
rg -n -A5 -B2 'width.*height|validate|bounds|clamp' --type ts -g '*imageCrop*' -g '*ImageCrop*'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 4715


🏁 Script executed:

# Also search more broadly for crop-related validation
rg -n 'crop.*valid|valid.*crop|bounds|clamp' --type ts src/renderer/extensions/vueNodes/widgets/

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 546


🏁 Script executed:

# Search for where the crop value is validated when an image loads
rg -n 'naturalWidth|naturalHeight|onload|image.*load' src/components/imagecrop/WidgetImageCrop.vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 139


🏁 Script executed:

# Check the useImageCrop composable to understand the validation flow
head -100 src/composables/useImageCrop.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 2647


🏁 Script executed:

# Search for where the crop bounds are adjusted based on image dimensions
rg -n 'modelValue|crop.*valid|ensureCropInBounds|normaliz' src/composables/useImageCrop.ts -A3

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 714


🏁 Script executed:

# Look for watchers or initialization that validate crop bounds when image loads
rg -n 'onload|imageEl\.value|naturalWidth|naturalHeight' src/components/imagecrop/WidgetImageCrop.vue -A5 -B2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

# Search for any validation that clamps default crop to image bounds
rg -n 'syncFromModelValue|clamp.*crop|validateCrop|ensureBounds' src/composables/useImageCrop.ts -A10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 808


🏁 Script executed:

# Check if there's any setup/initialization in the composable that validates default crop
sed -n '100,200p' src/composables/useImageCrop.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3071


🏁 Script executed:

# Look for where the crop is initialized or validated after image load
rg -n 'onload|imageEl.value?.naturalWidth|img.naturalWidth|imageEl.value?.onload' src/components/imagecrop/WidgetImageCrop.vue -B5 -A10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

# Check the full setup/initialize function to see if crop bounds are validated on mount
sed -n '410,435p' src/composables/useImageCrop.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 622


🏁 Script executed:

# Look for any validation that constrains the crop region to image bounds after image loads
rg -n 'constrainCrop|validateCropBounds|clamp.*crop|crop.*within|ensureCrop' src/composables/useImageCrop.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

# Search for the onload handler in WidgetImageCrop.vue
cat -n src/components/imagecrop/WidgetImageCrop.vue | head -150

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 5343


🏁 Script executed:

# Check if there's any watch on naturalWidth/naturalHeight that validates crop
rg -n 'watch.*naturalWidth|watch.*naturalHeight|updateDisplayedDimensions' src/composables/useImageCrop.ts -A8

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 808


🏁 Script executed:

# Look at the complete image element setup in the Vue component
sed -n '1,130p' src/components/imagecrop/WidgetImageCrop.vue

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3995


🏁 Script executed:

# Find the handleInputChange implementation
sed -n '290,320p' src/composables/useImageCrop.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 900


🏁 Script executed:

# Look at the complete handleImageLoad function
sed -n '280,300p' src/composables/useImageCrop.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 492


🏁 Script executed:

# Check if there's any validation after modelValue changes or on image load
rg -n 'handleImageLoad|handleInputChange' src/composables/useImageCrop.ts -A15

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1070


🏁 Script executed:

# Check if there's any watch on naturalWidth that would validate crop after image loads
sed -n '430,470p' src/composables/useImageCrop.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 684


Add bounds validation when image loads to constrain the default crop region.

The default crop region { x: 0, y: 0, width: 512, height: 512 } is applied without validation against actual image dimensions. When handleImageLoad() calls updateDisplayedDimensions(), the crop bounds should be clamped to the loaded image's natural dimensions. Currently, bounds checking only occurs during user interactions (drag/resize); if an image is smaller than 512×512 or has a different aspect ratio, the crop region could extend beyond the image bounds until the user manually adjusts it.

serialize: true,
...options
}
) as IImageCropWidget
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Avoid type assertion; consider improving type inference.

The type assertion as IImageCropWidget should be avoided per coding guidelines. The node.addWidget call should infer the correct type, or the widget API typing should be improved to eliminate the need for this assertion.

If the assertion is truly unavoidable due to the current widget API design, consider adding a comment explaining why it's necessary.

As per coding guidelines: "Avoid using @ts-expect-error; use proper TypeScript types instead" and "Type assertions are an absolute last resort."

🤖 Prompt for AI Agents
In @src/renderer/extensions/vueNodes/widgets/composables/useImageCropWidget.ts
around line 22, The code uses a type assertion "as IImageCropWidget" on the
result of node.addWidget; remove the assertion by fixing typings so the call
infers the correct widget type — either call node.addWidget with a generic
parameter (e.g., node.addWidget<IImageCropWidget>(...)) or update the widget API
signature so addWidget's return type is strongly typed for this widget. If
changing the API isn't possible now, keep the assertion but add a brief comment
above it explaining why the assertion is unavoidable and reference the related
typing limitation (IImageCropWidget / node.addWidget) so future refactors can
remove it.

Comment on lines +163 to +171
['load3D', { component: Load3D, aliases: ['LOAD_3D'], essential: false }],
[
'imagecrop',
{
component: WidgetImageCrop,
aliases: ['IMAGECROP'],
essential: false
}
]
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding 'imagecrop' to EXPANDING_TYPES for better UX.

The imagecrop widget includes an interactive image preview container with crop visualization, similar to load3D (which is in EXPANDING_TYPES at line 212). Adding 'imagecrop' to EXPANDING_TYPES would allow the widget to expand and utilize available vertical space, providing a better visual presentation and interaction area for users.

🔎 Proposed refactor
-const EXPANDING_TYPES = ['textarea', 'markdown', 'load3D'] as const
+const EXPANDING_TYPES = ['textarea', 'markdown', 'load3D', 'imagecrop'] as const

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

🤖 Prompt for AI Agents
In @src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts around
lines 163-171, The EXPANDING_TYPES set should include the 'imagecrop' widget so
it can expand vertically like 'load3D'; update the EXPANDING_TYPES declaration
(referencing the EXPANDING_TYPES symbol in widgetRegistry.ts) to add the
'imagecrop' key (matching the widget id used in the registry entry for component
WidgetImageCrop) so the image crop preview can use available vertical space and
improve UX.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants