Skip to content

Conversation

@AustinMroz
Copy link
Collaborator

@AustinMroz AustinMroz commented Nov 27, 2025

Continuation of #6034 with

  • Updated synchronization for seed
  • Properly truncates the displayed widget value for the button
  • Synchronizes control after generate state with litegraph and allows for serialization

Several issues from original PR have not (yet) been addressed, but are likely better moved to future PR

image

┆Issue is synchronized with this Notion page by Unito

christian-byrne and others added 12 commits November 11, 2025 11:49
The @PointerUp.stop was breaking reka ui NumberFields.

IIRC, this was added to allow selecting text without dragging nodes.
Current testing suggests this isn't required for pointerup

This reduces the margins some on number inputs. It's trivial to add a
px-2.5, but helps with information density
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Nov 27, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 2025

📝 Walkthrough

Walkthrough

Adds a number-control system: new control types and SafeControlWidget, mapping into graph payloads, composables for stepping and control application, a NumberControlRegistry with before/after hooks invoked during queue processing, UI components for selecting modes, widget linking for post-generation controls, locales, and tests.

Changes

Cohort / File(s) Summary
Types & graph mapping
src/types/simplifiedWidget.ts, src/composables/graph/useGraphNodeManager.ts
Added ControlOptions, normalizeControlOption, SafeControlWidget; extended SimplifiedWidget / SafeWidgetData with optional controlWidget and mapped it via a new getControlWidget.
Registry & app integration
src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts, src/scripts/app.ts
New NumberControlRegistry singleton and executeNumberControls; app calls executeNumberControls('before'/'after') around queuePrompt processing.
Control composables
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts, src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
New useStepperControl (NumberControlMode, applyControl, registry registration) and useNumberStepCalculation (centralized step derivation from step2/precision).
Widget creation linking
src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts, src/renderer/extensions/vueNodes/widgets/composables/useIntWidget.ts, src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
After-generation creation/linking of value-control widgets via addValueControlWidget when control_after_generate is enabled.
Number input UI components
src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue, .../NumberControlPopover.vue, .../WidgetInputNumber.vue, .../WidgetInputNumberInput.vue, .../WidgetInputNumberSlider.vue
Added WidgetInputNumberWithControl and NumberControlPopover; conditional rendering for control-enabled inputs; input/slider refactors and integration with stepper control.
Renderer payload & minor UI changes
src/renderer/extensions/vueNodes/components/NodeWidgets.vue, src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue, src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
Extended simplified widget payload with controlWidget; removed noop handlers in WidgetLayoutField; adjusted dropdown item concatenation logic.
Localization
src/locales/en/main.json
Added widgets.numberControl localization block (header keys and control action labels/descriptions).
Helpers & utilities
src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts, src/scripts/utils.ts
Added conditional linking in combo widget flow; removed two JSDoc tags in utils (no behavior change).
Tests & browser fixtures
tests-ui/tests/.../useStepperControl.test.ts, tests-ui/tests/.../NumberControlRegistry.test.ts, tests-ui/tests/.../useWidgetRenderer.test.ts, browser_tests/fixtures/VueNodeHelpers.ts, browser_tests/tests/.../integerWidget.spec.ts
Added unit tests for useStepperControl and NumberControlRegistry; improved Pinia test setup; adjusted test selectors for determinism.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WidgetComponent as Widget
    participant Popover as NumberControlPopover
    participant Registry as NumberControlRegistry
    participant App as App (queuePrompt)
    participant Settings as SettingsStore

    User->>Widget: open control popover
    Widget->>Popover: show
    User->>Popover: select mode
    Popover->>Widget: emit update:controlMode
    Widget->>Widget: call widget.controlWidget.update(mode)

    User->>App: queue prompt
    App->>Registry: executeNumberControls("before")
    Registry->>Settings: read WidgetControlMode
    Settings-->>Registry: current phase matches?
    Registry-->>App: execute registered callbacks (apply controls)

    App->>App: run generation

    App->>Registry: executeNumberControls("after")
    Registry->>Settings: read WidgetControlMode
    Settings-->>Registry: current phase matches?
    Registry-->>App: execute registered callbacks (post-generation)
Loading
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch austin/vue-control-after-generate

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfb579b and d25cd39.

📒 Files selected for processing (1)
  • src/locales/en/main.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/locales/**/*.json

📄 CodeRabbit inference engine (AGENTS.md)

Place new i18n translation entries in src/locales/en/main.json and use vue-i18n in Composition API for string literals

Files:

  • src/locales/en/main.json
⏰ 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: setup
  • GitHub Check: setup
  • GitHub Check: test
  • GitHub Check: collect
  • GitHub Check: lint-and-format

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 Nov 27, 2025

🎨 Storybook Build Status

Build completed successfully!

⏰ Completed at: 12/09/2025, 12:36:07 AM UTC

🔗 Links


🎉 Your Storybook is ready for review!

@github-actions
Copy link

github-actions bot commented Nov 27, 2025

🎭 Playwright Test Results

Some tests failed

⏰ Completed at: 12/09/2025, 12:47:40 AM UTC

📈 Summary

  • Total Tests: 473
  • Passed: 437 ✅
  • Failed: 18 ❌
  • Flaky: 8 ⚠️
  • Skipped: 10 ⏭️

📊 Test Reports by Browser

  • chromium: View Report • ✅ 428 / ❌ 18 / ⚠️ 8 / ⏭️ 10
  • chromium-2x: View Report • ✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0
  • chromium-0.5x: View Report • ✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0
  • mobile-chrome: View Report • ✅ 6 / ❌ 0 / ⚠️ 0 / ⏭️ 0

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

@github-actions
Copy link

github-actions bot commented Nov 27, 2025

Bundle Size Report

Summary

  • Raw size: 17.1 MB baseline 17.1 MB — 🔴 +16.7 kB
  • Gzip: 3.38 MB baseline 3.38 MB — 🔴 +4.46 kB
  • Brotli: 2.6 MB baseline 2.59 MB — 🔴 +3.91 kB
  • Bundles: 98 current • 97 baseline • 53 added / 52 removed

Category Glance
Other 🔴 +7.49 kB (3.82 MB) · UI Components 🔴 +6.19 kB (183 kB) · App Entry Points 🔴 +2.26 kB (3.21 MB) · Graph Workspace 🔴 +794 B (979 kB) · Vendor & Third-Party 🟢 -16 B (8.56 MB) · Panels & Settings ⚪ 0 B (298 kB) · + 3 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-BQIkYv7N.js (new) 2.98 MB 🔴 +2.98 MB 🔴 +621 kB 🔴 +472 kB
assets/index-C72CAjmz.js (removed) 2.98 MB 🟢 -2.98 MB 🟢 -620 kB 🟢 -471 kB
assets/index-12x7hJQD.js (removed) 223 kB 🟢 -223 kB 🟢 -47.6 kB 🟢 -39.2 kB
assets/index-C4TkWdPR.js (new) 223 kB 🔴 +223 kB 🔴 +47.6 kB 🔴 +39.2 kB
assets/index-B_VUwZB-.js (new) 345 B 🔴 +345 B 🔴 +245 B 🔴 +233 B
assets/index-XIo7IJaz.js (removed) 345 B 🟢 -345 B 🟢 -244 B 🟢 -228 B

Status: 3 added / 3 removed

Graph Workspace — 979 kB (baseline 978 kB) • 🔴 +794 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-CWMOV3o8.js (new) 979 kB 🔴 +979 kB 🔴 +189 kB 🔴 +144 kB
assets/GraphView-DiuxJXIJ.js (removed) 978 kB 🟢 -978 kB 🟢 -189 kB 🟢 -144 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/UserSelectView-B-uH3v_G.js (new) 6.54 kB 🔴 +6.54 kB 🔴 +2.14 kB 🔴 +1.9 kB
assets/UserSelectView-DPmyMQYH.js (removed) 6.54 kB 🟢 -6.54 kB 🟢 -2.14 kB 🟢 -1.9 kB

Status: 1 added / 1 removed

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CreditsPanel-DPajaAYX.js (new) 21.4 kB 🔴 +21.4 kB 🔴 +5.15 kB 🔴 +4.5 kB
assets/CreditsPanel-DX39ke6s.js (removed) 21.4 kB 🟢 -21.4 kB 🟢 -5.15 kB 🟢 -4.5 kB
assets/KeybindingPanel-B0fVlSBR.js (removed) 13.6 kB 🟢 -13.6 kB 🟢 -3.42 kB 🟢 -3 kB
assets/KeybindingPanel-DJHkmJGS.js (new) 13.6 kB 🔴 +13.6 kB 🔴 +3.42 kB 🔴 +3.02 kB
assets/ExtensionPanel-3w18qi2S.js (new) 10.8 kB 🔴 +10.8 kB 🔴 +2.57 kB 🔴 +2.26 kB
assets/ExtensionPanel-C62ksN6t.js (removed) 10.8 kB 🟢 -10.8 kB 🟢 -2.58 kB 🟢 -2.26 kB
assets/AboutPanel-BXhNvfhd.js (removed) 9.16 kB 🟢 -9.16 kB 🟢 -2.46 kB 🟢 -2.21 kB
assets/AboutPanel-Q-mYHACs.js (new) 9.16 kB 🔴 +9.16 kB 🔴 +2.46 kB 🔴 +2.21 kB
assets/ServerConfigPanel-BPvX2oY9.js (removed) 6.56 kB 🟢 -6.56 kB 🟢 -1.83 kB 🟢 -1.63 kB
assets/ServerConfigPanel-DgzUhZ6Q.js (new) 6.56 kB 🔴 +6.56 kB 🔴 +1.83 kB 🔴 +1.63 kB
assets/UserPanel-CoAUGqz_.js (new) 6.23 kB 🔴 +6.23 kB 🔴 +1.72 kB 🔴 +1.51 kB
assets/UserPanel-D_IECCpE.js (removed) 6.23 kB 🟢 -6.23 kB 🟢 -1.72 kB 🟢 -1.5 kB
assets/settings-BhbWhsRg.js 101 B 101 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BXTtSH4O.js 33.3 kB 33.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-C9Pzn-NG.js 25.2 kB 25.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CCy2fA_h.js 27.3 kB 27.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CQpqEFfl.js 26.6 kB 26.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DHcnxypw.js 21.7 kB 21.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DhFTK9fY.js 25.1 kB 25.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DlT4t_ui.js 25.9 kB 25.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DRgSrIdD.js 24.2 kB 24.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-tjkeqiZq.js 21.1 kB 21.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 6 added / 6 removed

UI Components — 183 kB (baseline 177 kB) • 🔴 +6.19 kB

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/Load3D.vue_vue_type_script_setup_true_lang-CaWtqe0t.js (new) 53.9 kB 🔴 +53.9 kB 🔴 +8.52 kB 🔴 +7.32 kB
assets/Load3D.vue_vue_type_script_setup_true_lang-XzAdQMy4.js (removed) 53.9 kB 🟢 -53.9 kB 🟢 -8.52 kB 🟢 -7.32 kB
assets/WidgetSelect.vue_vue_type_script_setup_true_lang-DLKS6cXQ.js (removed) 48 kB 🟢 -48 kB 🟢 -10.3 kB 🟢 -8.98 kB
assets/WidgetSelect.vue_vue_type_script_setup_true_lang-CFwxOx7d.js (new) 47.9 kB 🔴 +47.9 kB 🔴 +10.3 kB 🔴 +8.96 kB
assets/LazyImage.vue_vue_type_script_setup_true_lang-CSST_OMj.js (new) 47.3 kB 🔴 +47.3 kB 🔴 +10.6 kB 🔴 +9.28 kB
assets/LazyImage.vue_vue_type_script_setup_true_lang-fdO21-u8.js (removed) 47.3 kB 🟢 -47.3 kB 🟢 -10.6 kB 🟢 -9.29 kB
assets/WidgetInputNumber.vue_vue_type_script_setup_true_lang-BUpFwoja.js (new) 19.5 kB 🔴 +19.5 kB 🔴 +5.03 kB 🔴 +4.46 kB
assets/WidgetInputNumber.vue_vue_type_script_setup_true_lang-Cj8nUuGL.js (removed) 12.9 kB 🟢 -12.9 kB 🟢 -3.37 kB 🟢 -2.97 kB
assets/ComfyQueueButton-Ccpw67tX.js (removed) 8.44 kB 🟢 -8.44 kB 🟢 -2.48 kB 🟢 -2.21 kB
assets/ComfyQueueButton-V27U7RJf.js (new) 8.44 kB 🔴 +8.44 kB 🔴 +2.48 kB 🔴 +2.21 kB
assets/WidgetLayoutField.vue_vue_type_script_setup_true_lang-6ZIklFyS.js (removed) 2.26 kB 🟢 -2.26 kB 🟢 -878 B 🟢 -757 B
assets/WidgetButton-BPBNHSww.js (new) 2.04 kB 🔴 +2.04 kB 🔴 +929 B 🔴 +814 B
assets/WidgetButton-CFWrwaAG.js (removed) 2.04 kB 🟢 -2.04 kB 🟢 -926 B 🟢 -812 B
assets/WidgetLayoutField.vue_vue_type_script_setup_true_lang-gduOIO0o.js (new) 1.96 kB 🔴 +1.96 kB 🔴 +836 B 🔴 +713 B
assets/UserAvatar.vue_vue_type_script_setup_true_lang-BPGmgVoN.js (removed) 1.34 kB 🟢 -1.34 kB 🟢 -687 B 🟢 -596 B
assets/UserAvatar.vue_vue_type_script_setup_true_lang-DfX14YZi.js (new) 1.34 kB 🔴 +1.34 kB 🔴 +687 B 🔴 +591 B
assets/MediaTitle.vue_vue_type_script_setup_true_lang-BmWm9LMO.js (removed) 897 B 🟢 -897 B 🟢 -503 B 🟢 -436 B
assets/MediaTitle.vue_vue_type_script_setup_true_lang-DychLrDc.js (new) 897 B 🔴 +897 B 🔴 +503 B 🔴 +442 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-2LsDNdsL.js (removed) 7.51 kB 🟢 -7.51 kB 🟢 -1.83 kB 🟢 -1.58 kB
assets/keybindingService-B0HkvF_c.js (new) 7.51 kB 🔴 +7.51 kB 🔴 +1.83 kB 🔴 +1.58 kB
assets/serverConfigStore-CHweix5Z.js (new) 2.83 kB 🔴 +2.83 kB 🔴 +908 B 🔴 +794 B
assets/serverConfigStore-L3qzi_1Z.js (removed) 2.83 kB 🟢 -2.83 kB 🟢 -909 B 🟢 -791 B
assets/audioService-DtnQr58A.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -962 B 🟢 -822 B
assets/audioService-sAGX1jjk.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +964 B 🔴 +827 B

Status: 3 added / 3 removed

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

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/audioUtils-BDnuMZEt.js (new) 1.41 kB 🔴 +1.41 kB 🔴 +653 B 🔴 +553 B
assets/audioUtils-CVwH82Zq.js (removed) 1.41 kB 🟢 -1.41 kB 🟢 -653 B 🟢 -549 B
assets/mathUtil-CTARWQ-l.js 1.07 kB 1.07 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeFilterUtil-CXKCRJ-m.js 460 B 460 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 1 added / 1 removed

Vendor & Third-Party — 8.56 MB (baseline 8.56 MB) • 🟢 -16 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-other-BZV8aGUB.js (removed) 3.98 MB 🟢 -3.98 MB 🟢 -843 kB 🟢 -673 kB
assets/vendor-other-Dy4YSq-S.js (new) 3.98 MB 🔴 +3.98 MB 🔴 +843 kB 🔴 +673 kB
assets/vendor-primevue--zOs7uPS.js (new) 1.96 MB 🔴 +1.96 MB 🔴 +336 kB 🔴 +202 kB
assets/vendor-primevue-DUTcKlCc.js (removed) 1.96 MB 🟢 -1.96 MB 🟢 -336 kB 🟢 -202 kB
assets/vendor-chart-By6q2Ntx.js (new) 452 kB 🔴 +452 kB 🔴 +99.1 kB 🔴 +81 kB
assets/vendor-chart-DJFoH6N_.js (removed) 452 kB 🟢 -452 kB 🟢 -99.1 kB 🟢 -81 kB
assets/vendor-tiptap-Cmu0_BY4.js (removed) 232 kB 🟢 -232 kB 🟢 -45.7 kB 🟢 -37.7 kB
assets/vendor-tiptap-DhAV3_22.js (new) 232 kB 🔴 +232 kB 🔴 +45.7 kB 🔴 +37.7 kB
assets/vendor-vue-Bz22sFex.js (removed) 160 kB 🟢 -160 kB 🟢 -37.3 kB 🟢 -31.6 kB
assets/vendor-vue-CSdTo8s5.js (new) 160 kB 🔴 +160 kB 🔴 +37.3 kB 🔴 +31.6 kB
assets/vendor-three-aR6ntw5X.js 1.37 MB 1.37 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-BZLod3g9.js 407 kB 407 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Other — 3.82 MB (baseline 3.81 MB) • 🔴 +7.49 kB

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/WidgetRecordAudio-CO3v62tj.js (new) 20.4 kB 🔴 +20.4 kB 🔴 +5.24 kB 🔴 +4.63 kB
assets/WidgetRecordAudio-D7vSTYmV.js (removed) 20.4 kB 🟢 -20.4 kB 🟢 -5.24 kB 🟢 -4.63 kB
assets/AudioPreviewPlayer-BGMpoyt3.js (new) 13.5 kB 🔴 +13.5 kB 🔴 +3.4 kB 🔴 +3.04 kB
assets/AudioPreviewPlayer-DVX1kWYt.js (removed) 13.5 kB 🟢 -13.5 kB 🟢 -3.4 kB 🟢 -3.04 kB
assets/NumberControlPopover-BDztUVer.js (new) 7.49 kB 🔴 +7.49 kB 🔴 +2.16 kB 🔴 +1.9 kB
assets/WidgetGalleria-Bmk_Boh4.js (new) 4.1 kB 🔴 +4.1 kB 🔴 +1.45 kB 🔴 +1.3 kB
assets/WidgetGalleria-CS5X7S6E.js (removed) 4.1 kB 🟢 -4.1 kB 🟢 -1.45 kB 🟢 -1.3 kB
assets/WidgetColorPicker-BlkVTMqm.js (removed) 3.41 kB 🟢 -3.41 kB 🟢 -1.38 kB 🟢 -1.23 kB
assets/WidgetColorPicker-BXwAitgD.js (new) 3.41 kB 🔴 +3.41 kB 🔴 +1.38 kB 🔴 +1.23 kB
assets/WidgetMarkdown-CHwjM1rg.js (removed) 3.08 kB 🟢 -3.08 kB 🟢 -1.28 kB 🟢 -1.13 kB
assets/WidgetMarkdown-CWT5nZro.js (new) 3.08 kB 🔴 +3.08 kB 🔴 +1.28 kB 🔴 +1.12 kB
assets/WidgetTextarea-Cho4qK0E.js (new) 2.93 kB 🔴 +2.93 kB 🔴 +1.17 kB 🔴 +1.03 kB
assets/WidgetTextarea-Db76GQJF.js (removed) 2.93 kB 🟢 -2.93 kB 🟢 -1.17 kB 🟢 -1.03 kB
assets/WidgetAudioUI-70wLyMLg.js (new) 2.85 kB 🔴 +2.85 kB 🔴 +1.16 kB 🔴 +1.05 kB
assets/WidgetAudioUI-D-JG_6Fd.js (removed) 2.85 kB 🟢 -2.85 kB 🟢 -1.17 kB 🟢 -1.05 kB
assets/MediaVideoTop-C2NFR68N.js (new) 2.76 kB 🔴 +2.76 kB 🔴 +1.05 kB 🔴 +903 B
assets/MediaVideoTop-ehTZdDBw.js (removed) 2.76 kB 🟢 -2.76 kB 🟢 -1.05 kB 🟢 -901 B
assets/WidgetChart-CIwIkqhO.js (new) 2.48 kB 🔴 +2.48 kB 🔴 +932 B 🔴 +815 B
assets/WidgetChart-j6EYUdOM.js (removed) 2.48 kB 🟢 -2.48 kB 🟢 -932 B 🟢 -819 B
assets/WidgetImageCompare-D5bj5c8l.js (removed) 2.21 kB 🟢 -2.21 kB 🟢 -748 B 🟢 -659 B
assets/WidgetImageCompare-Hbirfb-r.js (new) 2.21 kB 🔴 +2.21 kB 🔴 +747 B 🔴 +659 B
assets/WidgetInputText-BJErep0a.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -916 B 🟢 -848 B
assets/WidgetInputText-C_J4O-Kz.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +917 B 🔴 +844 B
assets/MediaImageTop-BtY1hGDO.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -843 B 🟢 -718 B
assets/MediaImageTop-C4orQmj1.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +843 B 🔴 +714 B
assets/WidgetToggleSwitch-ch9OAEG7.js (new) 1.58 kB 🔴 +1.58 kB 🔴 +762 B 🔴 +665 B
assets/WidgetToggleSwitch-DPJMnc2A.js (removed) 1.58 kB 🟢 -1.58 kB 🟢 -759 B 🟢 -663 B
assets/MediaImageBottom-C3qIf9RW.js (removed) 1.57 kB 🟢 -1.57 kB 🟢 -745 B 🟢 -649 B
assets/MediaImageBottom-DQFzifvS.js (new) 1.57 kB 🔴 +1.57 kB 🔴 +739 B 🔴 +645 B
assets/MediaAudioBottom-D9GEIw7S.js (new) 1.52 kB 🔴 +1.52 kB 🔴 +740 B 🔴 +655 B
assets/MediaAudioBottom-EbQ6xYkF.js (removed) 1.52 kB 🟢 -1.52 kB 🟢 -746 B 🟢 -657 B
assets/MediaVideoBottom-DXm2Iwhk.js (new) 1.52 kB 🔴 +1.52 kB 🔴 +738 B 🔴 +653 B
assets/MediaVideoBottom-tCXcMkcv.js (removed) 1.52 kB 🟢 -1.52 kB 🟢 -742 B 🟢 -657 B
assets/Media3DBottom-B0bh5com.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +734 B 🔴 +651 B
assets/Media3DBottom-BWGoGSpo.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -731 B 🟢 -653 B
assets/Media3DTop-CD04ZQO0.js (new) 1.49 kB 🔴 +1.49 kB 🔴 +766 B 🔴 +654 B
assets/Media3DTop-EWE_9h7d.js (removed) 1.49 kB 🟢 -1.49 kB 🟢 -766 B 🟢 -653 B
assets/MediaAudioTop-BPDWO8-i.js (removed) 1.46 kB 🟢 -1.46 kB 🟢 -740 B 🟢 -616 B
assets/MediaAudioTop-DFa3tYwu.js (new) 1.46 kB 🔴 +1.46 kB 🔴 +740 B 🔴 +615 B
assets/WidgetSelect-BQqOlOwc.js (removed) 655 B 🟢 -655 B 🟢 -344 B 🟢 -290 B
assets/WidgetSelect-Cn-57mqc.js (new) 655 B 🔴 +655 B 🔴 +341 B 🔴 +285 B
assets/WidgetInputNumber-CtOhXQ8v.js (removed) 595 B 🟢 -595 B 🟢 -329 B 🟢 -278 B
assets/WidgetInputNumber-Dq26Rzk5.js (new) 595 B 🔴 +595 B 🔴 +328 B 🔴 +274 B
assets/Load3D-3np6qSB9.js (new) 424 B 🔴 +424 B 🔴 +267 B 🔴 +222 B
assets/Load3D-hF_JHJQI.js (removed) 424 B 🟢 -424 B 🟢 -268 B 🟢 -225 B
assets/WidgetLegacy-CkClDdVw.js (removed) 364 B 🟢 -364 B 🟢 -236 B 🟢 -202 B
assets/WidgetLegacy-DNl3dmwT.js (new) 364 B 🔴 +364 B 🔴 +238 B 🔴 +192 B
assets/commands-_s-RvhJR.js 13.6 kB 13.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BuUILW6P.js 13 kB 13 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BV4R6fLx.js 14.9 kB 14.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BWp4HdfU.js 101 B 101 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CLwPdnT6.js 14.2 kB 14.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CWMchBmd.js 15.9 kB 15.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DazTQhtc.js 12.9 kB 12.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DmWrOe93.js 13.7 kB 13.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DwiH7Kr6.js 13.8 kB 13.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-mS3LCNPn.js 14.5 kB 14.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B1JflQcI.js 72.2 kB 72.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B2lyXe48.js 114 kB 114 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B9XEQ-pc.js 94 kB 94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BErKFzc-.js 73.1 kB 73.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bf7Tze-u.js 83.4 kB 83.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BhGMcO4Q.js 84.3 kB 84.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CPZUloNQ.js 99 kB 99 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Cw9RZWRY.js 89 B 89 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Dva0z-T2.js 86.5 kB 86.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-un0K9wDS.js 81.8 kB 81.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BC3OlaIn.js 342 kB 342 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BsqN8-W1.js 285 kB 285 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Bw_Jitw_.js 101 B 101 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CK2saYDx.js 307 kB 307 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Cm5kR4Hi.js 306 kB 306 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CMrh-uxB.js 310 kB 310 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DlUIOit1.js 369 kB 369 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DNu_xoP2.js 282 kB 282 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DpcvlpZe.js 303 kB 303 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-IyjOYIl-.js 317 kB 317 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-BIbGSUAt.js 1.28 kB 1.28 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 24 added / 23 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: 3

🧹 Nitpick comments (8)
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue (2)

148-165: Unified dropdownItems logic looks correct

The refactor to have asset mode always use allItems and non-asset “all” explicitly concatenate inputItems and outputItems is consistent with the surrounding data flow; no functional issues stand out. If you want to reduce duplication, the 'all' branch could just return allItems.value, but that’s purely optional.


70-81: Localize user-facing strings and tighten upload error messaging

Several strings are user-visible but not run through vue-i18n, which conflicts with the repo guidelines:

  • Filter option labels: 'All', 'Inputs', 'Outputs' (Lines 76–79).
  • Toast messages: File upload failed, Upload failed: ${error} (Lines 296, 320).

Consider:

  • Replacing these literals with t(...) calls and adding appropriate entries in src/locales/en/main.json.
  • For the upload error toast, deriving a readable message (e.g. error instanceof Error ? error.message : String(error)) rather than stringifying the raw error object.

This keeps UX consistent and ready for localization without changing behavior.

Also applies to: 263-264, 295-321

tests-ui/tests/stores/globalSeedStore.test.ts (1)

21-28: Consider replacing the probabilistic assertion.

This test relies on random seed generation to be different across store instances, which introduces non-determinism. While the 1-in-1,000,000 chance is low, flaky tests can cause false failures in CI/CD pipelines.

Consider one of these alternatives:

  1. Mock Math.random() to return predictable values
  2. Test that the seed is within the valid range instead of comparing uniqueness
  3. Accept the low flakiness risk and document it clearly

Example with mocking:

 it('should create different seeds for different store instances', () => {
+  const mockRandom = vi.spyOn(Math, 'random')
+  mockRandom.mockReturnValueOnce(0.5)
   const store1 = useGlobalSeedStore()
   setActivePinia(createPinia()) // Reset pinia
+  mockRandom.mockReturnValueOnce(0.7)
   const store2 = useGlobalSeedStore()

-  // Very unlikely to be the same (1 in 1,000,000 chance)
-  expect(store1.globalSeed).not.toBe(store2.globalSeed)
+  expect(store1.globalSeed).toBe(500000)
+  expect(store2.globalSeed).toBe(700000)
+  mockRandom.mockRestore()
 })
src/types/simplifiedWidget.ts (1)

18-26: Consider adding 'link-to-global' to ControlWidgetOptions type.

The NumberControlPopover.vue component references a LINK_TO_GLOBAL mode (currently disabled via feature flag), but this option is missing from the ControlWidgetOptions type union. While the feature is disabled now, including it in the type definition would ensure type safety when the feature is enabled.

 export type ControlWidgetOptions =
   | 'fixed'
   | 'increment'
   | 'decrement'
   | 'randomize'
+  | 'link-to-global'
src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue (1)

29-29: Document the LINK_TO_GLOBAL feature flag.

The ENABLE_LINK_TO_GLOBAL constant is hardcoded to false, but there's no comment explaining why this feature is disabled or what would be required to enable it. Consider adding a comment to guide future development.

+// TODO: Enable LINK_TO_GLOBAL once global seed synchronization is fully implemented
 const ENABLE_LINK_TO_GLOBAL = false
src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue (1)

57-64: Consider using cn() utility for class merging.

Per coding guidelines, prefer using the cn() utility from @/utils/tailwindUtil for class merging instead of template string interpolation.

-        <i :class="`${controlButtonIcon} text-blue-100 text-xs`" />
+        <i :class="cn(controlButtonIcon, 'text-blue-100 text-xs')" />

You'll need to import cn from @/utils/tailwindUtil.

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

123-128: Remove potentially dead PrimeVue CSS.

This scoped CSS targets .p-inputnumber-input, a PrimeVue class. Since the component now uses Reka UI's NumberFieldRoot/NumberFieldInput, this CSS may no longer apply and could be removed.

src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts (1)

27-34: Consider wrapping callback execution in try-catch for resilience.

If one registered applyFn throws an error, it will prevent subsequent controls from being applied. Consider wrapping each callback in a try-catch to ensure all controls are executed.

   executeControls(phase: 'before' | 'after'): void {
     const settingStore = useSettingStore()
     if (settingStore.get('Comfy.WidgetControlMode') === phase) {
       for (const applyFn of this.controls.values()) {
-        applyFn()
+        try {
+          applyFn()
+        } catch (error) {
+          console.error('Error executing number control:', error)
+        }
       }
     }
   }

Comment on lines 54 to 67
if (typeof window !== 'undefined') {
import('@/base/common/downloadUtil')
.then((module) => {
const fn = (
module as {
downloadBlob?: typeof import('@/base/common/downloadUtil').downloadBlob
}
).downloadBlob
if (typeof fn === 'function') {
;(window as any).downloadBlob = fn
}
})
.catch(() => {})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix TypeScript violations: avoid as any and improve error handling.

This code violates two explicit coding guidelines:

  1. Line 63 uses as any type assertion
  2. Line 66 silently swallows errors

As per coding guidelines, never use as any type assertions and implement proper error handling.

Apply this diff to fix both issues:

+declare global {
+  interface Window {
+    downloadBlob?: typeof import('@/base/common/downloadUtil').downloadBlob
+  }
+}
+
 if (typeof window !== 'undefined') {
   import('@/base/common/downloadUtil')
     .then((module) => {
       const fn = (
         module as {
           downloadBlob?: typeof import('@/base/common/downloadUtil').downloadBlob
         }
       ).downloadBlob
       if (typeof fn === 'function') {
-        ;(window as any).downloadBlob = fn
+        window.downloadBlob = fn
       }
     })
-    .catch(() => {})
+    .catch((error) => {
+      console.error('Failed to load downloadBlob utility:', error)
+    })
 }
📝 Committable suggestion

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

Suggested change
if (typeof window !== 'undefined') {
import('@/base/common/downloadUtil')
.then((module) => {
const fn = (
module as {
downloadBlob?: typeof import('@/base/common/downloadUtil').downloadBlob
}
).downloadBlob
if (typeof fn === 'function') {
;(window as any).downloadBlob = fn
}
})
.catch(() => {})
}
declare global {
interface Window {
downloadBlob?: typeof import('@/base/common/downloadUtil').downloadBlob
}
}
if (typeof window !== 'undefined') {
import('@/base/common/downloadUtil')
.then((module) => {
const fn = (
module as {
downloadBlob?: typeof import('@/base/common/downloadUtil').downloadBlob
}
).downloadBlob
if (typeof fn === 'function') {
window.downloadBlob = fn
}
})
.catch((error) => {
console.error('Failed to load downloadBlob utility:', error)
})
}

@DrJKL DrJKL added the New Browser Test Expectations New browser test screenshot should be set by github action label Nov 27, 2025
@github-actions
Copy link

Updating Playwright Expectations

@github-actions github-actions bot removed the New Browser Test Expectations New browser test screenshot should be set by github action label Nov 27, 2025
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Nov 27, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

115-117: Add conditional rendering to prevent invisible hitbox.

The absolutely positioned wrapper renders unconditionally, creating an invisible overlay that can block input interaction when no slot is provided. This should only render when a default slot exists, matching the conditional padding logic.

Apply this diff:

-    <div class="absolute top-5 right-8 h-4 w-7 -translate-y-4/5">
+    <div
+      v-if="slots.default"
+      class="absolute top-5 right-8 h-4 w-7 -translate-y-4/5"
+    >
       <slot />
     </div>
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (1)

65-99: Fix inconsistent boundary constraint in LINK_TO_GLOBAL mode.

Line 88 uses min for the lower bound while all other modes consistently use safeMin. This inconsistency could allow values outside the safe integer range if min < -1125899906842624.

Apply this diff for consistency:

       case NumberControlMode.LINK_TO_GLOBAL:
         // Use global seed value, constrained by min/max
-        newValue = Math.max(min, Math.min(safeMax, globalSeedStore.globalSeed))
+        newValue = Math.max(safeMin, Math.min(safeMax, globalSeedStore.globalSeed))
         break
src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue (1)

26-37: Add runtime guard or tighten prop type to prevent unsafe non-null assertions.

Lines 32 and 37 use non-null assertions (widget.controlWidget!) without defensive checks. If controlWidget is undefined, this will cause a runtime error. While the parent component may conditionally render this component only when controlWidget exists, TypeScript cannot verify this guarantee.

Consider one of these solutions:

Option 1: Add a runtime guard at component entry:

const props = defineProps<{
  widget: SimplifiedWidget<number>
}>()

if (!props.widget.controlWidget) {
  throw new Error('WidgetInputNumberWithControl requires a widget with controlWidget')
}

// Then remove the `!` assertions below

Option 2: Tighten the prop type to require controlWidget:

const props = defineProps<{
  widget: SimplifiedWidget<number> & { controlWidget: SafeControlWidget }
}>()

// Then remove the `!` assertions below

Option 2 is preferred as it provides compile-time safety.

src/composables/graph/useGraphNodeManager.ts (1)

87-96: Use strict equality operator.

Line 89 uses loose equality (==) instead of strict equality (===). While this works for string comparison, TypeScript best practices recommend using === for type safety and consistency.

Apply this diff:

   const cagWidget = widget.linkedWidgets?.find(
-    (w) => w.name == 'control_after_generate'
+    (w) => w.name === 'control_after_generate'
   )

Based on coding guidelines, TypeScript should use strict equality for type safety.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b6d835 and 939090f.

📒 Files selected for processing (7)
  • src/composables/graph/useGraphNodeManager.ts (4 hunks)
  • src/locales/en/main.json (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue (4 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (1 hunks)
  • src/types/simplifiedWidget.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (18)
**/*.vue

📄 CodeRabbit inference engine (.cursorrules)

**/*.vue: Use setup() function for component logic in Vue 3 Composition API
Utilize ref and reactive for reactive state in Vue 3
Implement computed properties with computed() function
Use watch and watchEffect for side effects in Vue 3
Implement lifecycle hooks with onMounted, onUpdated, etc.
Utilize provide/inject for dependency injection in Vue 3
Use Vue 3.5 style of default prop declaration with defineProps()
Organize Vue components in <script> <style> order
Use Tailwind CSS for styling Vue components
Implement responsive design with Tailwind CSS
Do not use deprecated PrimeVue components (Dropdown, OverlayPanel, Calendar, InputSwitch, Sidebar, Chips, TabMenu, Steps, InlineMessage). Use replacements: Select, Popover, DatePicker, ToggleSwitch, Drawer, AutoComplete, Tabs, Stepper, Message respectively
Implement proper props and emits definitions in Vue components
Utilize Vue 3's Teleport component when needed
Use Suspense for async components in Vue 3
Follow Vue 3 style guide and naming conventions
Never use deprecated PrimeVue components (Dropdown, OverlayPanel, Calendar, InputSwitch, Sidebar, Chips, TabMenu, Steps, InlineMessage)

Never use :class="[]" to merge class names - always use import { cn } from '@/utils/tailwindUtil' for class merging in Vue templates

**/*.vue: Use TypeScript with Vue 3 Single File Components (.vue files)
Name Vue components in PascalCase (e.g., MenuHamburger.vue)

Files:

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

📄 CodeRabbit inference engine (.cursorrules)

**/*.{vue,ts,tsx}: Leverage VueUse functions for performance-enhancing utilities
Use vue-i18n in Composition API for any string literals and place new translation entries in src/locales/en/main.json

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
**/*.{ts,tsx,js,vue}

📄 CodeRabbit inference engine (.cursorrules)

Implement proper error handling in components and services

**/*.{ts,tsx,js,vue}: Use 2-space indentation, single quotes, no semicolons, and maintain 80-character line width as configured in .prettierrc
Organize imports by sorting and grouping by plugin, and run pnpm format before committing

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
src/**/*.vue

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

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

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
src/**/*.{vue,ts}

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

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

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use camelCase for variable and setting names in TypeScript/Vue files

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
**/*.{vue,html}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use dark: or dark-theme: Tailwind variants - instead use semantic values from style.css theme, e.g. bg-node-component-surface

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,vue}: Use const settingStore = useSettingStore() and settingStore.get('Comfy.SomeSetting') to retrieve settings in TypeScript/Vue files
Use await settingStore.set('Comfy.SomeSetting', newValue) to update settings in TypeScript/Vue files
Check server capabilities using api.serverSupportsFeature('feature_name') before using enhanced features
Use api.getServerFeature('config_name', defaultValue) to retrieve server feature configuration

Enforce ESLint rules for Vue + TypeScript including: no floating promises, no unused imports, and i18n raw text restrictions in templates

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
src/**/{composables,components}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Clean up subscriptions in state management to prevent memory leaks

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
src/**/*.{vue,ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
src/**/{components,composables}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Use es-toolkit for utility functions

Files:

  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/types/simplifiedWidget.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

Use TypeScript for type safety

**/*.{ts,tsx}: Never use any type - use proper TypeScript types
Never use as any type assertions - fix the underlying type issue

Files:

  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/types/simplifiedWidget.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/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/types/simplifiedWidget.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Define dynamic setting defaults using runtime context with functions in settings configuration
Use defaultsByInstallVersion property for gradual feature rollout based on version in settings configuration

Files:

  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/types/simplifiedWidget.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/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
**/composables/use*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name composables in the format useXyz.ts

Files:

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

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
📚 Learning: 2025-11-24T19:46:52.279Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-24T19:46:52.279Z
Learning: Applies to **/*.vue : Do not use deprecated PrimeVue components (Dropdown, OverlayPanel, Calendar, InputSwitch, Sidebar, Chips, TabMenu, Steps, InlineMessage). Use replacements: Select, Popover, DatePicker, ToggleSwitch, Drawer, AutoComplete, Tabs, Stepper, Message respectively

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
📚 Learning: 2025-11-24T19:46:52.279Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-24T19:46:52.279Z
Learning: Applies to **/*.vue : Never use deprecated PrimeVue components (Dropdown, OverlayPanel, Calendar, InputSwitch, Sidebar, Chips, TabMenu, Steps, InlineMessage)

Applied to files:

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

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.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 setup() function in Vue 3 Composition API

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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 : Implement computed() for derived state in Vue 3 Composition API

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
📚 Learning: 2025-11-24T19:46:52.279Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-24T19:46:52.279Z
Learning: Applies to **/*.vue : Implement computed properties with computed() function

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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 : Implement computed properties with computed()

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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,ts} : Leverage VueUse functions for performance-enhancing styles

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
📚 Learning: 2025-11-24T19:46:52.279Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-24T19:46:52.279Z
Learning: Applies to **/*.{vue,ts,tsx} : Leverage VueUse functions for performance-enhancing utilities

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
📚 Learning: 2025-11-24T19:46:52.279Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-24T19:46:52.279Z
Learning: Applies to **/*.vue : Use setup() function for component logic in Vue 3 Composition API

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
📚 Learning: 2025-11-24T19:47:14.779Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:14.779Z
Learning: Applies to **/*.vue : Never use `:class="[]"` to merge class names - always use `import { cn } from '@/utils/tailwindUtil'` for class merging in Vue templates

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
📚 Learning: 2025-11-24T19:48:23.088Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:48:23.088Z
Learning: Applies to **/composables/use*.ts : Name composables in the format `useXyz.ts`

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.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/graph/useGraphNodeManager.ts
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue OverlayPanel component with Popover

Applied to files:

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

Applied to files:

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

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
🧬 Code graph analysis (1)
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (3)
src/types/simplifiedWidget.ts (1)
  • ControlOptions (24-24)
src/stores/globalSeedStore.ts (1)
  • useGlobalSeedStore (4-16)
src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts (1)
  • numberControlRegistry (52-52)
⏰ 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: collect
  • GitHub Check: lint-and-format
  • GitHub Check: test
  • GitHub Check: setup
🔇 Additional comments (12)
src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue (2)

3-3: LGTM!

The useSlots import is correctly added and used to detect the presence of a default slot for conditional styling.


72-72: LGTM!

The slot detection and conditional padding logic is well-implemented. The right padding is correctly applied only when a default slot is present.

Also applies to: 94-98

src/types/simplifiedWidget.ts (2)

18-30: LGTM!

The control options validation is well-implemented. The exclusion of 'linkToGlobal' is intentional since the feature is currently disabled (ENABLE_LINK_TO_GLOBAL = false in NumberControlPopover.vue). The default fallback to 'randomize' provides safe behavior for unknown values.


32-35: LGTM!

The SafeControlWidget type provides a clean API for control interaction with a value getter and update callback. The optional controlWidget field in SimplifiedWidget correctly enables the control feature without breaking existing widgets.

Also applies to: 68-68

src/composables/graph/useGraphNodeManager.ts (2)

23-24: LGTM!

The imports are correctly added for the new control widget functionality.


130-131: LGTM!

The controlWidget is correctly populated in the widget mapper using the helper function. The integration is clean and follows the existing pattern.

src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (3)

9-23: LGTM!

The NumberControlMode enum and StepperControlOptions interface are well-defined. Including LINK_TO_GLOBAL in the enum despite the feature being currently disabled is good forward-planning for future enablement.


25-37: LGTM!

The convertToEnum function correctly handles all enabled control options. The absence of 'linkToGlobal' is intentional since the feature is currently disabled (ENABLE_LINK_TO_GLOBAL = false).


39-54: LGTM!

The icon mapping is comprehensive and uses appropriate icons for each control mode.

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

1-87: LGTM!

The component is well-structured using Vue 3 Composition API. The feature flag for LINK_TO_GLOBAL is appropriately placed, and the component API (props, emits, expose) is clean and well-defined.

As per coding guidelines, the component correctly uses PrimeVue Popover (not the deprecated OverlayPanel) and ToggleSwitch (not the deprecated InputSwitch).


89-171: LGTM!

The template is well-structured with proper use of semantic CSS classes, i18n for all user-facing text, and clean conditional rendering. The layout is responsive and accessible.

As per coding guidelines, all user-facing strings are correctly internationalized using vue-i18n.

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

45-67: LGTM!

The template is well-structured with proper component composition and grid layout. The button and popover integration is clean.

Comment on lines +1914 to 1931
"numberControl": {
"header" : {
"prefix": "Automatically update the value",
"after": "AFTER",
"before": "BEFORE",
"postfix": "running the workflow:"
},
"linkToGlobal": "Link to",
"linkToGlobalSeed": "Global Value",
"linkToGlobalDesc": "Unique value linked to the Global Value's control setting",
"randomize": "Randomize Value",
"randomizeDesc": "Shuffles the value randomly after each generation",
"increment": "Increment Value",
"incrementDesc": "Adds 1 to the value number",
"decrement": "Decrement Value",
"decrementDesc": "Subtracts 1 from the value number",
"editSettings": "Edit control settings"
}
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

Verify intentional ALL CAPS usage for mode labels.

Lines 1917-1918 use ALL CAPS for "AFTER" and "BEFORE" while surrounding text uses mixed case. This creates visual emphasis but is inconsistent with typical UI text conventions.

Please confirm this capitalization is intentional for design/UX reasons. If the goal is to emphasize the control mode, consider alternative approaches like bold styling or a separate UI treatment rather than ALL CAPS in the localization string.

Example alternative structure:

"header": {
  "prefix": "Automatically update the value",
  "afterMode": "after",
  "beforeMode": "before",
  "postfix": "running the workflow:"
}

Then apply emphasis via CSS in the component rather than hardcoding capitalization.

🤖 Prompt for AI Agents
In src/locales/en/main.json around lines 1914 to 1931, the header labels "AFTER"
and "BEFORE" are in ALL CAPS while surrounding text is mixed case; confirm
whether ALL CAPS is intentional for UX, and if not replace those values with
normal-cased alternatives (e.g., "after"/"before" or "After"/"Before") and
optionally rename the keys to semantic names like afterMode/beforeMode so
emphasis is applied in the component via styling/CSS instead of hardcoded
capitalization.

// precision 1 → 0.1, precision 2 → 0.01, etc.
return 1 / Math.pow(10, precision.value)
})
const stepValue = useNumberStepCalculation(widget.options, precision, true)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this could be a utility function instead of a composable for now.

Comment on lines +9 to +15
export enum NumberControlMode {
FIXED = 'fixed',
INCREMENT = 'increment',
DECREMENT = 'decrement',
RANDOMIZE = 'randomize',
LINK_TO_GLOBAL = 'linkToGlobal'
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
export enum NumberControlMode {
FIXED = 'fixed',
INCREMENT = 'increment',
DECREMENT = 'decrement',
RANDOMIZE = 'randomize',
LINK_TO_GLOBAL = 'linkToGlobal'
}
export const NumberControlMode = {
FIXED: 'fixed',
INCREMENT: 'increment',
DECREMENT: 'decrement',
RANDOMIZE: 'randomize',
LINK_TO_GLOBAL: 'linkToGlobal'
} as const

That thing we talked about


const applyControl = () => {
const { min = 0, max = 1000000, step2, step = 1, onChange } = options
const safeMax = Math.min(1125899906842624, max)
Copy link
Contributor

Choose a reason for hiding this comment

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


// Register with singleton registry
onMounted(() => {
numberControlRegistry.register(controlId, applyControl)
Copy link
Contributor

Choose a reason for hiding this comment

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

Double check to see if you need to be DOM dependent here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Followup: sorta. It's being used as a convenient way to ensure it gets unregistered at some point. Since I'm proposing removal in a future PR, it's probably not worth rewriting the implementation here.

@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Dec 6, 2025
@AustinMroz AustinMroz added the New Browser Test Expectations New browser test screenshot should be set by github action label Dec 6, 2025
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: 5

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

113-115: Conditionally render the slot wrapper to avoid invisible hitbox.

This issue was previously flagged: the absolutely positioned wrapper div renders unconditionally, creating an invisible hitbox over the input's top-right area when no default slot is provided. This can intercept clicks/focus without showing any control.

Apply this diff to align the wrapper rendering with the hasSlot computed property:

     </InputNumber>
-    <div class="absolute top-5 right-8 h-4 w-7 -translate-y-4/5">
+    <div
+      v-if="hasSlot"
+      class="absolute top-5 right-8 h-4 w-7 -translate-y-4/5"
+    >
       <slot />
     </div>
   </WidgetLayoutField>

This keeps the overlay and extra padding in sync and avoids an invisible hitbox when no control is slotted in.

Based on learnings, as per coding guidelines: Always use cn() from @/utils/tailwindUtil to merge class names.

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

72-80: Guard MultiSelect control wiring with isComboWidget to avoid numeric-control path

addMultiSelectWidget wires addValueControlWidgets whenever inputSpec.control_after_generate is set, but it doesn’t assert that the ComponentWidgetImpl is actually treated as a combo widget. If isComboWidget(widget) ever returns false, addValueControlWidgets will take the numeric path (using min/max/step2) against a multi-select widget, which can lead to incorrect behavior or type mismatches at runtime.

Mirroring the defensive pattern used in createInputMappingWidget / addComboWidget here keeps behavior predictable:

   // TODO: Add remote support to multi-select widget
   // https://github.com/Comfy-Org/ComfyUI_frontend/issues/3003
   if (inputSpec.control_after_generate) {
+    if (!isComboWidget(widget)) {
+      throw new Error(`Expected combo widget but received ${widget.type}`)
+    }
     widget.linkedWidgets = addValueControlWidgets(
       node,
       widget,
       'fixed',
       undefined,
       transformInputSpecV2ToV1(inputSpec)
     )
   }
src/locales/en/main.json (1)

1914-1931: Align increment/decrement descriptions (and optionally casing) with actual behavior

The control descriptions currently say:

  • "incrementDesc": "Adds 1 to the value number"
  • "decrementDesc": "Subtracts 1 from the value number"

but the underlying logic uses the widget’s step (step/step2), not a literal 1. That can confuse users for non‑unit steps (e.g. 0.5, 5, 10).

Consider wording them in terms of the step amount instead:

-      "incrementDesc": "Adds 1 to the value number",
-      "decrementDesc": "Subtracts 1 from the value number",
+      "incrementDesc": "Adds the step amount to the value",
+      "decrementDesc": "Subtracts the step amount from the value",

Also, the ALL‑CAPS "AFTER" / "BEFORE" in the header are visually inconsistent with surrounding copy; if this emphasis isn’t intentional, you may want to normalize their casing as noted in the earlier review.

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

24-36: Missing 'linkToGlobal' case in convertToEnum.

The convertToEnum function does not handle 'linkToGlobal'. When ENABLE_LINK_TO_GLOBAL is enabled in NumberControlPopover.vue and a user selects that mode, the value will incorrectly fall through to the default case and return RANDOMIZE.

Apply this diff to add the missing case:

 function convertToEnum(str?: ControlOptions): NumberControlMode {
   switch (str) {
     case 'fixed':
       return NumberControlMode.FIXED
     case 'increment':
       return NumberControlMode.INCREMENT
     case 'decrement':
       return NumberControlMode.DECREMENT
     case 'randomize':
       return NumberControlMode.RANDOMIZE
+    case 'linkToGlobal':
+      return NumberControlMode.LINK_TO_GLOBAL
   }
   return NumberControlMode.RANDOMIZE
 }
src/types/simplifiedWidget.ts (1)

18-24: Missing 'linkToGlobal' in CONTROL_OPTIONS array.

The CONTROL_OPTIONS array does not include 'linkToGlobal', but NumberControlMode.LINK_TO_GLOBAL exists in useStepperControl.ts and NumberControlPopover.vue conditionally uses it when ENABLE_LINK_TO_GLOBAL is true. This creates a type inconsistency where valid enum values cannot be normalized correctly.

Apply this diff:

 const CONTROL_OPTIONS = [
   'fixed',
   'increment',
   'decrement',
-  'randomize'
+  'randomize',
+  'linkToGlobal'
 ] as const
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 939090f and 1fa618c.

📒 Files selected for processing (15)
  • src/composables/graph/useGraphNodeManager.ts (4 hunks)
  • src/locales/en/main.json (1 hunks)
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue (2 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue (3 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts (2 hunks)
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts (3 hunks)
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (1 hunks)
  • src/scripts/utils.ts (0 hunks)
  • src/types/simplifiedWidget.ts (2 hunks)
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts (1 hunks)
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • src/scripts/utils.ts
🧰 Additional context used
📓 Path-based instructions (16)
src/**/*.vue

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

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

Files:

  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
src/**/*.{vue,ts}

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

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

Files:

  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/types/simplifiedWidget.ts
src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/types/simplifiedWidget.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/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
src/**/*.{vue,ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/types/simplifiedWidget.ts
src/**/{components,composables}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

**/*.vue: Use Vue 3 SFCs with Composition API only (use <script setup lang="ts">, not Options API)
Avoid using <style> blocks; use Tailwind 4 for all styling
Use defineProps with TypeScript style default declaration; do not use withDefaults or runtime props declaration
Prefer useModel over separately defining a prop and emit
Use computed instead of a ref and watch if possible
Avoid using ref if a prop would accomplish the design goals; avoid using computed if a ref or prop directly would work
Do not import Vue macros unnecessarily
Never use the dark: Tailwind variant; use semantic values from the style.css theme instead (e.g., bg-node-component-surface)
Never use :class="[]" to merge class names; always use cn() from @/utils/tailwindUtil (e.g., <div :class="cn('text-node-component-header-icon', hasError && 'text-danger')" />)
Leverage VueUse functions for performance-enhancing styles
Avoid new usage of PrimeVue components
Use Vue 3 Teleport component when needed
Use Vue 3 Suspense for async components

Files:

  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
**/*.{ts,vue}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,vue}: Use camelCase for variable and function names
Indent with 2 spaces (see .prettierrc)
Use single quotes for strings (see .prettierrc)
No trailing semicolons (see .prettierrc)
Maximum line width of 80 characters (see .prettierrc)
Sort and group imports by plugin (run pnpm format before committing)
Never use any type; use proper TypeScript types instead
Never use as any type assertions; fix the underlying type issue instead
Avoid code comments unless absolutely necessary; write expressive, self-documenting code instead
When writing new code, ask if there is a simpler way to introduce the same functionality; if yes, choose the simpler approach
Use refactoring to make complex code simpler
Use es-toolkit for utility functions
Use Vite for fast development and building
Implement proper error handling
Write tests for all changes, especially bug fixes to catch future regressions

Files:

  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/types/simplifiedWidget.ts
**/components/**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

Name Vue components in PascalCase (e.g., MenuHamburger.vue)

Files:

  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
src/locales/**/*.json

📄 CodeRabbit inference engine (AGENTS.md)

Place new i18n translation entries in src/locales/en/main.json and use vue-i18n in Composition API for string literals

Files:

  • src/locales/en/main.json
tests-ui/**/*.test.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (tests-ui/CLAUDE.md)

tests-ui/**/*.test.{js,ts,jsx,tsx}: Write tests for new features
Follow existing test patterns in the codebase
Use existing test utilities rather than writing custom utilities
Mock external dependencies in tests
Always prefer vitest mock functions over writing verbose manual mocks

Files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript exclusively; no new JavaScript code

Files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/types/simplifiedWidget.ts
**/composables/**/use*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name composables with useXyz.ts pattern

Files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/composables/graph/useGraphNodeManager.ts
**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.ts: Avoid writing change detector tests that just assert default values
Avoid writing tests dependent on non-behavioral features like utility classes or styles
Avoid writing redundant tests

Files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.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/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/types/simplifiedWidget.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/useFloatWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/composables/graph/useGraphNodeManager.ts
🧠 Learnings (36)
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue InputSwitch component with ToggleSwitch

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.vue : Never use `:class="[]"` to merge class names; always use `cn()` from `@/utils/tailwindUtil` (e.g., `<div :class="cn('text-node-component-header-icon', hasError && 'text-danger')" />`)

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.{ts,vue} : Use es-toolkit for utility functions

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.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 setup() function in Vue 3 Composition API

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.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 provide/inject for dependency injection

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.vue : Use Vue 3 SFCs with Composition API only (use `<script setup lang="ts">`, not Options API)

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.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 setup() function for component logic

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
📚 Learning: 2025-11-24T19:47:34.324Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:34.324Z
Learning: Applies to src/**/*.{ts,tsx,vue} : Use es-toolkit for utility functions instead of other utility libraries

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
📚 Learning: 2025-11-24T19:48:03.270Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: tests-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:48:03.270Z
Learning: Applies to tests-ui/**/*.test.{js,ts,jsx,tsx} : Mock external dependencies in tests

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:48:03.270Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: tests-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:48:03.270Z
Learning: Applies to tests-ui/**/*.test.{js,ts,jsx,tsx} : Write tests for new features

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:48:03.270Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: tests-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:48:03.270Z
Learning: Applies to tests-ui/**/*.test.{js,ts,jsx,tsx} : Always prefer vitest mock functions over writing verbose manual mocks

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:48:03.270Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: tests-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:48:03.270Z
Learning: Applies to tests-ui/**/*.test.{js,ts,jsx,tsx} : Use existing test utilities rather than writing custom utilities

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.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/**/*.{test,spec}.{ts,tsx} : Use provided test helpers `createTestSubgraph` and `createTestSubgraphNode` from `./fixtures/subgraphHelpers` for consistent subgraph test setup

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.{ts,vue} : Write tests for all changes, especially bug fixes to catch future regressions

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:48:03.270Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: tests-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:48:03.270Z
Learning: Applies to tests-ui/**/*.test.{js,ts,jsx,tsx} : Follow existing test patterns in the codebase

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:48:09.318Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .cursor/rules/unit-test.mdc:0-0
Timestamp: 2025-11-24T19:48:09.318Z
Learning: Applies to test/**/*.{test,spec}.{js,ts,jsx,tsx} : Prefer the use of `test.extend` over loose variables; import `test as baseTest` from `vitest`

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to src/**/*.test.ts : Write unit and component tests with Vitest using happy-dom

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
📚 Learning: 2025-11-24T19:47:22.909Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: browser_tests/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:22.909Z
Learning: Applies to browser_tests/**/*.{e2e,spec}.{ts,tsx,js,jsx} : Use Playwright fixtures for browser tests

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
📚 Learning: 2025-11-24T19:48:09.318Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .cursor/rules/unit-test.mdc:0-0
Timestamp: 2025-11-24T19:48:09.318Z
Learning: Applies to test/**/*.{test,spec}.{js,ts,jsx,tsx} : Use `vitest` for unit testing in this project

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
📚 Learning: 2025-11-24T19:48:09.318Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .cursor/rules/unit-test.mdc:0-0
Timestamp: 2025-11-24T19:48:09.318Z
Learning: Applies to test/**/*.{test,spec}.{js,ts,jsx,tsx} : Use `test` instead of `it` for defining test cases in vitest

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue Steps component with Stepper without panels

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
📚 Learning: 2025-11-24T19:48:03.270Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: tests-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:48:03.270Z
Learning: Check tests-ui/README.md for test guidelines

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.test.ts : Avoid writing change detector tests that just assert default values

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.test.ts : Avoid writing tests dependent on non-behavioral features like utility classes or styles

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.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 : Utilize ref and reactive for reactive state

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.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 : Use ref/reactive for state management in Vue 3 Composition API

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.vue : Use `computed` instead of a `ref` and `watch` if possible

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.vue : Avoid using `ref` if a prop would accomplish the design goals; avoid using `computed` if a `ref` or prop directly would work

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
📚 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/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.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 : Implement computed() for derived state in Vue 3 Composition API

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.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/**/*.{vue,ts,tsx} : Follow Vue 3 composition API style guide

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.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/renderer/extensions/vueNodes/widgets/composables/useStepperControl.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/graph/useGraphNodeManager.ts
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue OverlayPanel component with Popover

Applied to files:

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

Applied to files:

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

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
🧬 Code graph analysis (5)
src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts (2)
src/scripts/widgets.ts (1)
  • addValueControlWidget (79-102)
src/schemas/nodeDef/migration.ts (1)
  • transformInputSpecV2ToV1 (139-143)
tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts (2)
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (1)
  • useStepperControl (55-111)
src/renderer/core/layout/store/layoutStore.ts (1)
  • onChange (926-929)
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (3)
src/types/simplifiedWidget.ts (1)
  • ControlOptions (24-24)
src/renderer/core/layout/store/layoutStore.ts (1)
  • onChange (926-929)
src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts (1)
  • numberControlRegistry (52-52)
src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts (2)
src/scripts/widgets.ts (1)
  • addValueControlWidgets (104-286)
src/schemas/nodeDef/migration.ts (1)
  • transformInputSpecV2ToV1 (139-143)
src/composables/graph/useGraphNodeManager.ts (1)
src/types/simplifiedWidget.ts (2)
  • SafeControlWidget (35-38)
  • normalizeControlOption (30-33)
⏰ 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: setup
  • GitHub Check: lint-and-format
  • GitHub Check: setup
  • GitHub Check: test
  • GitHub Check: collect
🔇 Additional comments (13)
src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue (1)

32-34: Event propagation simplification looks good

Replacing noop handlers with bare .stop modifiers keeps the intended behavior (blocking pointer event bubbling beyond this container) while removing unnecessary code. Slot children still receive pointer events, and no regressions are apparent for layout or interaction.

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

92-96: Use useSlots() for reactive slot detection in the :pt binding.

The $slots.default reference inside the :pt object binding may not be reactive when slot presence changes dynamically. In Vue 3 Composition API, slot access should use the useSlots() composable for guaranteed reactivity.

Apply this diff to ensure reactive slot detection:

 <script setup lang="ts">
 import InputNumber from 'primevue/inputnumber'
-import { computed } from 'vue'
+import { computed, useSlots } from 'vue'
 
 import type { SimplifiedWidget } from '@/types/simplifiedWidget'
 import { cn } from '@/utils/tailwindUtil'
@@ -18,6 +18,9 @@

 const modelValue = defineModel<number>({ default: 0 })
 
+const slots = useSlots()
+const hasSlot = computed(() => !!slots.default)
+
 const filteredProps = computed(() =>
   filterWidgetProps(props.widget.options, INPUT_EXCLUDED_PROPS)
 )

Then update the :pt binding:

       :pt="{
         root: {
           class: cn(
             '[&>input]:bg-transparent [&>input]:border-0',
             '[&>input]:truncate [&>input]:min-w-[4ch]',
-            $slots.default && '[&>input]:pr-7'
+            hasSlot.value && '[&>input]:pr-7'
           )
         },
⛔ Skipped due to learnings
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.vue : Never use `:class="[]"` to merge class names; always use `cn()` from `@/utils/tailwindUtil` (e.g., `<div :class="cn('text-node-component-header-icon', hasError && 'text-danger')" />`)
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.vue : Avoid new usage of PrimeVue components
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 the Vue 3 Composition API instead of the Options API when writing Vue components (exception: when overriding or extending PrimeVue components for compatibility)
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: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue InputSwitch component with ToggleSwitch
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.vue : Avoid using `ref` if a prop would accomplish the design goals; avoid using `computed` if a `ref` or prop directly would work
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.vue : Use `computed` instead of a `ref` and `watch` if possible
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.vue : Use Vue 3 SFCs with Composition API only (use `<script setup lang="ts">`, not Options API)
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
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
src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts (1)

20-25: Imports for combo control wiring look correct

Using isComboInputSpec, transformInputSpecV2ToV1, and addValueControlWidgets here aligns this composable with the rest of the control-after-generate infrastructure; no issues from a typing or dependency perspective.

tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts (1)

1-3: Improved test isolation with Pinia and settings mock

Using createTestingPinia in beforeEach plus a focused useSettingStore mock gives these widget registry tests a clean, deterministic environment without leaking Pinia state across cases. This looks solid.

Also applies to: 31-42

tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts (1)

33-190: Comprehensive coverage of stepper control behavior

These tests exercise all the control modes, min/max clamping, default options, and onChange semantics, with appropriate mocking for the global seed store and the registry. This gives good confidence that useStepperControl will behave correctly across typical and edge scenarios.

Also applies to: 192-272

src/renderer/extensions/vueNodes/components/NodeWidgets.vue (1)

157-166: Correctly propagating control widget metadata into SimplifiedWidget

Forwarding both spec and the new controlWidget field into simplified keeps the Vue components in sync with the LiteGraph widget state and the graph manager’s SafeWidgetData. This is the right place to expose the control metadata; looks good.

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

60-61: Float widget control-after-generate wiring is consistent and type-safe

Capturing the float widget in a local widget variable and, when inputSpec.control_after_generate is set, creating a control widget via addValueControlWidget (with the V2→V1-transformed spec) and assigning widget.linkedWidgets = [controlWidget] matches the established pattern for numeric controls. The parameter ordering and default 'fixed' mode are correct.

Also applies to: 79-89, 91-92

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

59-60: The original concern about null handling in handleNumberInputUpdate is based on incorrect assumptions about PrimeVue InputNumber behavior

PrimeVue InputNumber does NOT emit update:modelValue when the user clears the field. According to PrimeVue's behavior, update:modelValue only fires on validated changes (blur, Enter, or spinner clicks). Clearing with the clear icon does not trigger this event at all—it only fires the input event if anything.

The current code at lines 59–60 handles undefined correctly for its actual purpose: the :key="timesEmptied" binding (line 13) forces InputNumber to re-render and reset its internal state when timesEmptied increments. Since update:modelValue doesn't emit anything on clear, the newValue !== undefined check works as intended for the limited cases where InputNumber does emit (blur, Enter, spinner).

The concern about null propagating into a number-typed model is not applicable here because InputNumber's update:modelValue doesn't emit null on clear. If clearing behavior needs to be handled differently, consider using the @input event instead, but no change is required for the current undefined handling.

Likely an incorrect or invalid review comment.

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

13-35: LGTM! Well-structured step calculation logic.

The composable correctly:

  • Prioritizes explicit step2 values over precision-derived steps
  • Uses toValue with MaybeRefOrGetter for flexible reactivity (as previously suggested)
  • Handles the returnUndefinedForDefault flag appropriately
  • Returns a computed ref for efficient reactivity
src/types/simplifiedWidget.ts (2)

26-38: SafeControlWidget type and helper functions look good.

The type guard isControlOption and normalization function normalizeControlOption provide safe runtime validation with appropriate fallback behavior. The SafeControlWidget type cleanly encapsulates control state and updates.


71-71: Good addition of controlWidget to SimplifiedWidget.

Adding the optional controlWidget field enables the control-after-generate feature while maintaining backward compatibility.

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

89-170: Excellent use of vue-i18n and semantic styling.

The template correctly:

  • Uses $t() for all user-facing strings with entries in src/locales/en/main.json
  • Applies semantic Tailwind classes (bg-interface-panel-surface, text-muted-foreground, etc.) instead of dark: variants
  • Provides clear visual hierarchy and accessibility structure

As per coding guidelines.


29-60: Feature flag pattern is clean, but verify LINK_TO_GLOBAL implementation.

The ENABLE_LINK_TO_GLOBAL flag cleanly controls the experimental feature. However, enabling this feature will expose the issues flagged in useStepperControl.ts (missing convertToEnum case and missing applyControl switch case) and simplifiedWidget.ts (missing 'linkToGlobal' in CONTROL_OPTIONS).

Ensure that the flagged issues in the related files are resolved before enabling this feature.

Comment on lines +87 to +96
function getControlWidget(widget: IBaseWidget): SafeControlWidget | undefined {
const cagWidget = widget.linkedWidgets?.find(
(w) => w.name == 'control_after_generate'
)
if (!cagWidget) return
return {
value: normalizeControlOption(cagWidget.value),
update: (value) => (cagWidget.value = normalizeControlOption(value))
}
}
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

Use strict equality when locating the control widget

getControlWidget currently uses loose equality:

const cagWidget = widget.linkedWidgets?.find(
  (w) => w.name == 'control_after_generate'
)

Since w.name is a string, there’s no benefit to == over ===, and sticking to strict equality avoids surprises and matches the rest of the codebase’s style.

-  const cagWidget = widget.linkedWidgets?.find(
-    (w) => w.name == 'control_after_generate'
-  )
+  const cagWidget = widget.linkedWidgets?.find(
+    (w) => w.name === 'control_after_generate'
+  )

The rest of the helper (normalizing the value on read/write and exposing controlWidget via safeWidgetMapper) looks good.

Also applies to: 130-132

🤖 Prompt for AI Agents
In src/composables/graph/useGraphNodeManager.ts around lines 87 to 96 (and
similarly at lines 130 to 132), the find predicate uses loose equality when
comparing widget names; change the comparison from == to strict === to match
codebase style and avoid surprises, i.e. update the predicate to use w.name ===
'control_after_generate' in both locations so the control widget is located with
strict equality.

Comment on lines +2 to +4
import Button from 'primevue/button'
import Popover from 'primevue/popover'
import ToggleSwitch from 'primevue/toggleswitch'
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 future migration away from PrimeVue components.

This component uses PrimeVue's Button, Popover, and ToggleSwitch. The coding guidelines state "Avoid new usage of PrimeVue components," and retrieved learnings mention replacing these with native alternatives. While this may be acceptable within the current architecture, plan to migrate away from PrimeVue in future refactoring.

Based on coding guidelines.

🤖 Prompt for AI Agents
In src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
around lines 2 to 4, this file imports PrimeVue Button, Popover, and
ToggleSwitch which violates the "avoid new usage of PrimeVue components"
guideline; replace direct PrimeVue usage by creating/using local native or
framework-agnostic equivalents (e.g., a simple native <button> wrapper, a
lightweight popover component implemented with Portal/teleport and CSS, and a
toggle built from input[type="checkbox"] or an existing internal Toggle
component), or introduce small adapter wrappers (e.g., LocalButton,
LocalPopover, LocalToggle) and update the template and props to use those
wrappers so the component no longer depends on PrimeVue and is easier to migrate
later.

Comment on lines +63 to +86
const applyControl = () => {
const { min = 0, max = 1000000, step2, step = 1, onChange } = options
const safeMax = Math.min(2 ** 50, max)
const safeMin = Math.max(-(2 ** 50), min)
// Use step2 if available (widget context), otherwise use step as-is (direct API usage)
const actualStep = step2 !== undefined ? step2 : step

let newValue: number
switch (controlMode.value) {
case NumberControlMode.FIXED:
// Do nothing - keep current value
return
case NumberControlMode.INCREMENT:
newValue = Math.min(safeMax, modelValue.value + actualStep)
break
case NumberControlMode.DECREMENT:
newValue = Math.max(safeMin, modelValue.value - actualStep)
break
case NumberControlMode.RANDOMIZE:
newValue = Math.floor(Math.random() * (safeMax - safeMin + 1)) + safeMin
break
default:
return
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing LINK_TO_GLOBAL case in applyControl logic.

The applyControl function's switch statement (lines 71-86) does not include a case for NumberControlMode.LINK_TO_GLOBAL, even though the enum defines it and useControlButtonIcon handles it. When this mode is active, the function will fall through to the default case and return early without applying any control.

Apply this diff to add the missing case:

     case NumberControlMode.RANDOMIZE:
       newValue = Math.floor(Math.random() * (safeMax - safeMin + 1)) + safeMin
       break
+    case NumberControlMode.LINK_TO_GLOBAL:
+      // Use global seed value, constrained by min/max
+      // Note: Requires globalSeedStore import and implementation
+      // Placeholder for when feature is enabled
+      return
     default:
       return
   }

Note: The actual implementation will require importing and using a global seed store, as referenced in the past review comments.

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

🤖 Prompt for AI Agents
In src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
around lines 63 to 86 the applyControl switch is missing a case for
NumberControlMode.LINK_TO_GLOBAL; add a case that reads the global seed from the
shared/global seed store (import the store at top), set newValue to that global
seed while clamping it between safeMin and safeMax, and then break so the value
is applied like other modes; ensure the store import and access follow existing
project patterns (reactive getter or accessor) and handle undefined/missing
global seed by falling back to modelValue or a sensible default before clamping.

Comment on lines +65 to +66
const safeMax = Math.min(2 ** 50, max)
const safeMin = Math.max(-(2 ** 50), min)
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 Number.MAX_SAFE_INTEGER for clarity.

The safe bounds use 2 ** 50, which is within JavaScript's safe integer range but less explicit than the standard constant.

Apply this diff:

-    const safeMax = Math.min(2 ** 50, max)
-    const safeMin = Math.max(-(2 ** 50), min)
+    const safeMax = Math.min(Number.MAX_SAFE_INTEGER, max)
+    const safeMin = Math.max(-Number.MAX_SAFE_INTEGER, min)
📝 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 safeMax = Math.min(2 ** 50, max)
const safeMin = Math.max(-(2 ** 50), min)
const safeMax = Math.min(Number.MAX_SAFE_INTEGER, max)
const safeMin = Math.max(-Number.MAX_SAFE_INTEGER, min)
🤖 Prompt for AI Agents
In src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
around lines 65-66, replace the hardcoded 2 ** 50 bounds with the standard
constants: set safeMax using Math.min(Number.MAX_SAFE_INTEGER, max) and set
safeMin using Math.max(Number.MIN_SAFE_INTEGER, min) so the intent is explicit
and uses built-in safe-integer constants.

Comment on lines +134 to +161
it('should randomize value in RANDOMIZE mode', () => {
const modelValue = ref(100)
const options = { min: 0, max: 10, step: 1 }

const { controlMode, applyControl } = useStepperControl(
modelValue,
options
)
controlMode.value = NumberControlMode.RANDOMIZE

applyControl()

// Value should be within bounds
expect(modelValue.value).toBeGreaterThanOrEqual(0)
expect(modelValue.value).toBeLessThanOrEqual(10)

// Run multiple times to check randomness (value should change at least once)
for (let i = 0; i < 10; i++) {
const beforeValue = modelValue.value
applyControl()
if (modelValue.value !== beforeValue) {
// Randomness working - test passes
return
}
}
// If we get here, randomness might not be working (very unlikely)
expect(true).toBe(true) // Still pass the test
})
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

Simplify RANDOMIZE test to assert behavior deterministically

The RANDOMIZE test already verifies that the resulting value stays within [min, max], which is the key behavior. The follow-up loop that tries to detect that the value “changes at least once” but ultimately falls back to expect(true).toBe(true) doesn’t add real signal and can be dropped to keep the test focused and deterministic.

You could safely remove the change-detection loop and keep only the bounds assertions.

🤖 Prompt for AI Agents
In
tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
around lines 134 to 161, the RANDOMIZE test contains a non-deterministic loop
that tries to detect value changes and ends with a no-op fallback
(expect(true).toBe(true)); remove the entire for-loop and the fallback
assertion, leaving only the deterministic assertions that the modelValue.value
is within the min and max bounds after applyControl() to keep the test focused
and reliable.

@github-actions github-actions bot removed the New Browser Test Expectations New browser test screenshot should be set by github action label Dec 6, 2025
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Dec 6, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts (1)

127-154: Simplify RANDOMIZE test to focus on deterministic behavior.

The non-deterministic loop (lines 143-153) that tries to detect value changes and falls back to expect(true).toBe(true) doesn't add meaningful signal. The bounds assertions (lines 140-141) already validate the key behavior.

Remove the loop and keep only the bounds assertions:

     it('should randomize value in RANDOMIZE mode', () => {
       const modelValue = ref(100)
       const options = { min: 0, max: 10, step: 1 }
 
       const { controlMode, applyControl } = useStepperControl(
         modelValue,
         options
       )
       controlMode.value = NumberControlMode.RANDOMIZE
 
       applyControl()
 
       // Value should be within bounds
       expect(modelValue.value).toBeGreaterThanOrEqual(0)
       expect(modelValue.value).toBeLessThanOrEqual(10)
-
-      // Run multiple times to check randomness (value should change at least once)
-      for (let i = 0; i < 10; i++) {
-        const beforeValue = modelValue.value
-        applyControl()
-        if (modelValue.value !== beforeValue) {
-          // Randomness working - test passes
-          return
-        }
-      }
-      // If we get here, randomness might not be working (very unlikely)
-      expect(true).toBe(true) // Still pass the test
     })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1fa618c and cfb579b.

⛔ Files ignored due to path filters (16)
  • browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/canvas/zoom.spec.ts-snapshots/zoomed-in-ctrl-shift-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/node/move.spec.ts-snapshots/vue-node-moved-node-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/nodeStates/bypass.spec.ts-snapshots/vue-node-bypassed-state-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/nodeStates/mute.spec.ts-snapshots/vue-node-muted-state-chromium-linux.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
tests-ui/**/*.test.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (tests-ui/CLAUDE.md)

tests-ui/**/*.test.{js,ts,jsx,tsx}: Write tests for new features
Follow existing test patterns in the codebase
Use existing test utilities rather than writing custom utilities
Mock external dependencies in tests
Always prefer vitest mock functions over writing verbose manual mocks

Files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript exclusively; no new JavaScript code

Files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
**/*.{ts,vue}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,vue}: Use camelCase for variable and function names
Indent with 2 spaces (see .prettierrc)
Use single quotes for strings (see .prettierrc)
No trailing semicolons (see .prettierrc)
Maximum line width of 80 characters (see .prettierrc)
Sort and group imports by plugin (run pnpm format before committing)
Never use any type; use proper TypeScript types instead
Never use as any type assertions; fix the underlying type issue instead
Avoid code comments unless absolutely necessary; write expressive, self-documenting code instead
When writing new code, ask if there is a simpler way to introduce the same functionality; if yes, choose the simpler approach
Use refactoring to make complex code simpler
Use es-toolkit for utility functions
Use Vite for fast development and building
Implement proper error handling
Write tests for all changes, especially bug fixes to catch future regressions

Files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
**/composables/**/use*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name composables with useXyz.ts pattern

Files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.ts: Avoid writing change detector tests that just assert default values
Avoid writing tests dependent on non-behavioral features like utility classes or styles
Avoid writing redundant tests

Files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
🧠 Learnings (12)
📚 Learning: 2025-11-24T19:48:03.270Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: tests-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:48:03.270Z
Learning: Applies to tests-ui/**/*.test.{js,ts,jsx,tsx} : Write tests for new features

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.{ts,vue} : Write tests for all changes, especially bug fixes to catch future regressions

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:48:03.270Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: tests-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:48:03.270Z
Learning: Applies to tests-ui/**/*.test.{js,ts,jsx,tsx} : Use existing test utilities rather than writing custom utilities

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue Steps component with Stepper without panels

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:48:03.270Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: tests-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:48:03.270Z
Learning: Applies to tests-ui/**/*.test.{js,ts,jsx,tsx} : Follow existing test patterns in the codebase

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:48:03.270Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: tests-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:48:03.270Z
Learning: Applies to tests-ui/**/*.test.{js,ts,jsx,tsx} : Mock external dependencies in tests

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:48:03.270Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: tests-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:48:03.270Z
Learning: Check tests-ui/README.md for test guidelines

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.test.ts : Avoid writing change detector tests that just assert default values

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.test.ts : Avoid writing tests dependent on non-behavioral features like utility classes or styles

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:48:09.318Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .cursor/rules/unit-test.mdc:0-0
Timestamp: 2025-11-24T19:48:09.318Z
Learning: Applies to test/**/*.{test,spec}.{js,ts,jsx,tsx} : Prefer the use of `test.extend` over loose variables; import `test as baseTest` from `vitest`

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-12-06T00:52:35.750Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T00:52:35.750Z
Learning: Applies to **/*.test.ts : Avoid writing redundant tests

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:48:03.270Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: tests-ui/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:48:03.270Z
Learning: Applies to tests-ui/**/*.test.{js,ts,jsx,tsx} : Always prefer vitest mock functions over writing verbose manual mocks

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
🧬 Code graph analysis (1)
tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts (1)
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (1)
  • useStepperControl (55-111)
⏰ 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). (4)
  • GitHub Check: test
  • GitHub Check: lint-and-format
  • GitHub Check: setup
  • GitHub Check: collect

Comment on lines +11 to +24
// Mock the registry to spy on calls
vi.mock(
'@/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry',
() => ({
numberControlRegistry: {
register: vi.fn(),
unregister: vi.fn(),
executeControls: vi.fn(),
getControlCount: vi.fn(() => 0),
clear: vi.fn()
},
executeNumberControls: vi.fn()
})
)
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 verifying registry registration behavior.

The mock is correctly set up, but none of the tests verify that register and unregister are called during the composable's lifecycle (onMounted/onUnmounted). Since the implementation registers the control with the singleton registry, this is testable behavior that should be verified.

Add a test to verify the lifecycle hooks:

+  describe('registry integration', () => {
+    it('should register control on mount and unregister on unmount', async () => {
+      const { numberControlRegistry } = await import(
+        '@/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry'
+      )
+      const modelValue = ref(100)
+      const options = { min: 0, max: 1000, step: 1 }
+
+      const wrapper = mount({
+        setup() {
+          return useStepperControl(modelValue, options)
+        },
+        template: '<div />'
+      })
+
+      expect(numberControlRegistry.register).toHaveBeenCalledTimes(1)
+      expect(numberControlRegistry.register).toHaveBeenCalledWith(
+        expect.any(Symbol),
+        expect.any(Function)
+      )
+
+      wrapper.unmount()
+
+      expect(numberControlRegistry.unregister).toHaveBeenCalledTimes(1)
+    })
+  })

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

Comment on lines +42 to +53
it('should return control mode and apply function', () => {
const modelValue = ref(100)
const options = { min: 0, max: 1000, step: 1 }

const { controlMode, applyControl } = useStepperControl(
modelValue,
options
)

expect(controlMode.value).toBe(NumberControlMode.RANDOMIZE)
expect(typeof applyControl).toBe('function')
})
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 consolidating initialization tests.

This test overlaps significantly with the previous test (line 33), both checking the default mode is RANDOMIZE. The additional type check for applyControl is useful but could be merged into a single, more comprehensive initialization test to reduce redundancy.

As per coding guidelines: Avoid writing redundant tests.

🤖 Prompt for AI Agents
tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
lines 42-53: duplicate initialization test — merge this block with the earlier
test around line 33 so there is a single comprehensive initialization test that
asserts the default controlMode is NumberControlMode.RANDOMIZE and also checks
that applyControl is a function; remove the redundant test case and update the
combined test to include both expectations (default mode and applyControl type)
to avoid repetition.

Comment on lines +56 to +155
describe('control modes', () => {
it('should not change value in FIXED mode', () => {
const modelValue = ref(100)
const options = { min: 0, max: 1000, step: 1 }

const { controlMode, applyControl } = useStepperControl(
modelValue,
options
)
controlMode.value = NumberControlMode.FIXED

applyControl()
expect(modelValue.value).toBe(100)
})

it('should increment value in INCREMENT mode', () => {
const modelValue = ref(100)
const options = { min: 0, max: 1000, step: 5 }

const { controlMode, applyControl } = useStepperControl(
modelValue,
options
)
controlMode.value = NumberControlMode.INCREMENT

applyControl()
expect(modelValue.value).toBe(105)
})

it('should decrement value in DECREMENT mode', () => {
const modelValue = ref(100)
const options = { min: 0, max: 1000, step: 5 }

const { controlMode, applyControl } = useStepperControl(
modelValue,
options
)
controlMode.value = NumberControlMode.DECREMENT

applyControl()
expect(modelValue.value).toBe(95)
})

it('should respect min/max bounds for INCREMENT', () => {
const modelValue = ref(995)
const options = { min: 0, max: 1000, step: 10 }

const { controlMode, applyControl } = useStepperControl(
modelValue,
options
)
controlMode.value = NumberControlMode.INCREMENT

applyControl()
expect(modelValue.value).toBe(1000) // Clamped to max
})

it('should respect min/max bounds for DECREMENT', () => {
const modelValue = ref(5)
const options = { min: 0, max: 1000, step: 10 }

const { controlMode, applyControl } = useStepperControl(
modelValue,
options
)
controlMode.value = NumberControlMode.DECREMENT

applyControl()
expect(modelValue.value).toBe(0) // Clamped to min
})

it('should randomize value in RANDOMIZE mode', () => {
const modelValue = ref(100)
const options = { min: 0, max: 10, step: 1 }

const { controlMode, applyControl } = useStepperControl(
modelValue,
options
)
controlMode.value = NumberControlMode.RANDOMIZE

applyControl()

// Value should be within bounds
expect(modelValue.value).toBeGreaterThanOrEqual(0)
expect(modelValue.value).toBeLessThanOrEqual(10)

// Run multiple times to check randomness (value should change at least once)
for (let i = 0; i < 10; i++) {
const beforeValue = modelValue.value
applyControl()
if (modelValue.value !== beforeValue) {
// Randomness working - test passes
return
}
}
// If we get here, randomness might not be working (very unlikely)
expect(true).toBe(true) // Still pass the test
})
})
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

Add test coverage for step2 vs step behavior.

The implementation uses step2 when available and falls back to step (see useStepperControl.ts lines 67-68), but no tests verify this code path. Since step2 is used in widget contexts, this is important behavior to test.

Add tests for step2 behavior:

+    it('should prefer step2 over step for INCREMENT when both provided', () => {
+      const modelValue = ref(100)
+      const options = { min: 0, max: 1000, step: 1, step2: 10 }
+
+      const { controlMode, applyControl } = useStepperControl(
+        modelValue,
+        options
+      )
+      controlMode.value = NumberControlMode.INCREMENT
+
+      applyControl()
+      expect(modelValue.value).toBe(110) // step2 should be used
+    })
+
+    it('should prefer step2 over step for DECREMENT when both provided', () => {
+      const modelValue = ref(100)
+      const options = { min: 0, max: 1000, step: 1, step2: 10 }
+
+      const { controlMode, applyControl } = useStepperControl(
+        modelValue,
+        options
+      )
+      controlMode.value = NumberControlMode.DECREMENT
+
+      applyControl()
+      expect(modelValue.value).toBe(90) // step2 should be used
+    })
🤖 Prompt for AI Agents
In
tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
around lines 56-155, add unit tests that explicitly cover the step2 vs step
behavior: create test cases where options include both step (e.g. 1) and step2
(e.g. 7) and assert that INCREMENT/DECREMENT change modelValue by step2 (±7) and
respect min/max when using step2; also add a test where step2 is undefined to
confirm fallback to step (e.g. step=5 causes ±5). Keep tests consistent with the
existing pattern (use ref for modelValue, set controlMode, call applyControl,
assert expected values and clamping).

@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Dec 9, 2025
@AustinMroz AustinMroz added New Browser Test Expectations New browser test screenshot should be set by github action size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Dec 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude-review Add to trigger a PR code review from Claude Code New Browser Test Expectations New browser test screenshot should be set by github action size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants