Skip to content

Conversation

@AustinMroz
Copy link
Collaborator

@AustinMroz AustinMroz commented Nov 27, 2025

Continuation of #6034 with

  • Updated synchronization for seed
  • Migrates NumberWidgetNumberInput to Reka UI so the control after generate button doesn't overlap with text
  • Sychronizes 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

  • fix step value being 10 (legacy system)
  • ensure it works with COMBO
  • ensure it works with FLOAT
  • either implement or remove the config button functionality - think it should open settings?
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 types, composables, UI components, a registry and global seed store; wires control widgets into widget creation and renderer payloads; executes registered controls around prompt queuing.

Changes

Cohort / File(s) Summary
Types
src/types/simplifiedWidget.ts
Added ControlWidgetOptions and SafeControlWidget; added optional controlWidget?: SafeControlWidget to SimplifiedWidget.
Graph Mapper
src/composables/graph/useGraphNodeManager.ts
Extended SafeWidgetData with controlWidget?; added helpers to derive/validate control widget from linked control_after_generate widget and include it in safeWidgetMapper.
Widget renderer payload
src/renderer/extensions/vueNodes/components/NodeWidgets.vue
Include controlWidget: widget.controlWidget in the serialized simplified widget payload.
Number-control composables
src/renderer/extensions/vueNodes/widgets/composables/*
useStepperControl.ts, useNumberStepCalculation.ts, useControlButtonIcon.ts
Added useStepperControl and NumberControlMode, added useNumberStepCalculation to centralize step logic, and added useControlButtonIcon mapping modes → icon.
Number control UI
src/renderer/extensions/vueNodes/widgets/components/*
WidgetInputNumberWithControl.vue, NumberControlPopover.vue, WidgetInputNumber.vue, WidgetInputNumberInput.vue, WidgetInputNumberSlider.vue
New combined input+control component and popover; prefer input-with-control when control widget exists; migrate number input from PrimeVue InputNumber to reka-ui NumberField; delegate slider step to useNumberStepCalculation; treat 0 as valid input.
Widget composables integration
src/renderer/extensions/vueNodes/widgets/composables/*
useFloatWidget.ts, useIntWidget.ts, useComboWidget.ts
Conditional creation/linking of value-control widgets when inputSpec.control_after_generate is set (via addValueControlWidget with transformed spec).
Registry & execution
src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts, src/scripts/app.ts
New NumberControlRegistry singleton with register/unregister/execute/getCount/clear; executeNumberControls(phase) invoked in app queue flow before/after prompt handling.
Global seed
src/stores/globalSeedStore.ts
New Pinia store useGlobalSeedStore exposing globalSeed and setGlobalSeed.
Utilities & minor UI
src/scripts/utils.ts, src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue, src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
Exposed browser downloadBlob dynamically; removed two JSDoc lines; dropdown now concatenates input/output items; removed inner @pointerup.stop.
Localization
src/locales/en/main.json
Added widgets.numberControl localization strings (labels, option titles/descriptions, BEFORE/AFTER).
Tests
tests-ui/tests/*
Added/updated tests: useStepperControl.test.ts, NumberControlRegistry.test.ts, globalSeedStore.test.ts, adapted widget renderer tests and NumberField migration tests to reflect component and behavior changes.

Sequence Diagram(s)

sequenceDiagram
    participant User as UI
    participant Widget as Widget Component
    participant Control as useStepperControl
    participant Store as GlobalSeedStore
    participant Registry as NumberControlRegistry

    User->>Widget: change number input
    Widget->>Control: applyControl / onChange
    Control->>Control: determine NumberControlMode
    alt RANDOMIZE
        Control->>Store: read globalSeed
        Store-->>Control: seed
        Control->>Control: compute random value (min..max)
    else LINK_TO_GLOBAL
        Control->>Store: read and clamp globalSeed
        Store-->>Control: clamped value
    else INCREMENT/DECREMENT
        Control->>Control: add/subtract step and clamp
    else FIXED
        Control->>Control: no change
    end
    Control-->>Widget: return updated value
    Widget-->>User: render updated value
Loading
sequenceDiagram
    participant App as App (queuePrompt)
    participant Registry as NumberControlRegistry
    participant Setting as Setting Store
    participant Controls as Registered Controls

    App->>Registry: executeNumberControls('before')
    Registry->>Setting: get('Comfy.WidgetControlMode')
    Setting-->>Registry: mode
    alt mode == 'before'
        Registry->>Controls: invoke all callbacks
    end
    App->>App: process queued prompt(s)
    App->>Registry: executeNumberControls('after')
    Registry->>Setting: get('Comfy.WidgetControlMode')
    Setting-->>Registry: mode
    alt mode == 'after'
        Registry->>Controls: invoke all callbacks
    end
Loading

Possibly related PRs

✨ Finishing touches
  • 📝 Generate docstrings
🧪 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

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: 11/30/2025, 09:58:32 PM 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: 11/30/2025, 10:10:14 PM UTC

📈 Summary

  • Total Tests: 476
  • Passed: 445 ✅
  • Failed: 18 ❌
  • Flaky: 4 ⚠️
  • Skipped: 9 ⏭️

📊 Test Reports by Browser

  • chromium: View Report • ✅ 436 / ❌ 18 / ⚠️ 4 / ⏭️ 9
  • 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 MB — 🔴 +61.5 kB
  • Gzip: 3.38 MB baseline 3.37 MB — 🔴 +13.5 kB
  • Brotli: 2.59 MB baseline 2.58 MB — 🔴 +10.6 kB
  • Bundles: 98 current • 97 baseline • 53 added / 52 removed

Category Glance
Vendor & Third-Party 🔴 +43.2 kB (8.6 MB) · Other 🔴 +7.51 kB (3.85 MB) · UI Components 🔴 +7.35 kB (147 kB) · App Entry Points 🔴 +2.77 kB (3.18 MB) · Graph Workspace 🔴 +729 B (951 kB) · Panels & Settings ⚪ 0 B (298 kB) · + 3 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-qNHYW0i6.js (new) 2.95 MB 🔴 +2.95 MB 🔴 +616 kB 🔴 +468 kB
assets/index-BMCtxuTH.js (removed) 2.95 MB 🟢 -2.95 MB 🟢 -616 kB 🟢 -467 kB
assets/index-D1cQd6yS.js (removed) 227 kB 🟢 -227 kB 🟢 -48.6 kB 🟢 -40 kB
assets/index-DuUhz8l1.js (new) 227 kB 🔴 +227 kB 🔴 +48.6 kB 🔴 +40 kB
assets/index-D8e8mh8S.js (removed) 345 B 🟢 -345 B 🟢 -245 B 🟢 -234 B
assets/index-DoSyXHf_.js (new) 345 B 🔴 +345 B 🔴 +247 B 🔴 +237 B

Status: 3 added / 3 removed

Graph Workspace — 951 kB (baseline 950 kB) • 🔴 +729 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-DC4bgJM7.js (new) 951 kB 🔴 +951 kB 🔴 +184 kB 🔴 +141 kB
assets/GraphView-DpPFz-o4.js (removed) 950 kB 🟢 -950 kB 🟢 -184 kB 🟢 -141 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-BYdHdfP0.js (new) 6.54 kB 🔴 +6.54 kB 🔴 +2.14 kB 🔴 +1.9 kB
assets/UserSelectView-CdyzggDX.js (removed) 6.54 kB 🟢 -6.54 kB 🟢 -2.14 kB 🟢 -1.89 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-BbR4Io1m.js (removed) 21.4 kB 🟢 -21.4 kB 🟢 -5.15 kB 🟢 -4.5 kB
assets/CreditsPanel-CPI_00hy.js (new) 21.4 kB 🔴 +21.4 kB 🔴 +5.15 kB 🔴 +4.5 kB
assets/KeybindingPanel-C-zErxWf.js (new) 13.6 kB 🔴 +13.6 kB 🔴 +3.42 kB 🔴 +3.01 kB
assets/KeybindingPanel-y3MZlHeZ.js (removed) 13.6 kB 🟢 -13.6 kB 🟢 -3.42 kB 🟢 -3.01 kB
assets/ExtensionPanel-D5dNfU21.js (new) 10.8 kB 🔴 +10.8 kB 🔴 +2.58 kB 🔴 +2.25 kB
assets/ExtensionPanel-DmGkQHep.js (removed) 10.8 kB 🟢 -10.8 kB 🟢 -2.57 kB 🟢 -2.25 kB
assets/AboutPanel-DBHuqGM_.js (removed) 9.16 kB 🟢 -9.16 kB 🟢 -2.46 kB 🟢 -2.21 kB
assets/AboutPanel-Dk0aPmsR.js (new) 9.16 kB 🔴 +9.16 kB 🔴 +2.46 kB 🔴 +2.21 kB
assets/ServerConfigPanel-BFooSNxX.js (removed) 6.56 kB 🟢 -6.56 kB 🟢 -1.83 kB 🟢 -1.63 kB
assets/ServerConfigPanel-H-Ptqh5_.js (new) 6.56 kB 🔴 +6.56 kB 🔴 +1.83 kB 🔴 +1.63 kB
assets/UserPanel-CmAAOyfr.js (new) 6.23 kB 🔴 +6.23 kB 🔴 +1.72 kB 🔴 +1.51 kB
assets/UserPanel-DaBtjoFT.js (removed) 6.23 kB 🟢 -6.23 kB 🟢 -1.71 kB 🟢 -1.51 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 — 147 kB (baseline 139 kB) • 🔴 +7.35 kB

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/Load3D.vue_vue_type_script_setup_true_lang-Bk3QYI3I.js (new) 53.9 kB 🔴 +53.9 kB 🔴 +8.52 kB 🔴 +7.31 kB
assets/Load3D.vue_vue_type_script_setup_true_lang-PMWLIUD_.js (removed) 53.9 kB 🟢 -53.9 kB 🟢 -8.52 kB 🟢 -7.31 kB
assets/WidgetSelect.vue_vue_type_script_setup_true_lang-C4m4TTvK.js (removed) 47 kB 🟢 -47 kB 🟢 -10.1 kB 🟢 -8.79 kB
assets/WidgetSelect.vue_vue_type_script_setup_true_lang-BiS8WmMX.js (new) 47 kB 🔴 +47 kB 🔴 +10.1 kB 🔴 +8.78 kB
assets/WidgetInputNumber.vue_vue_type_script_setup_true_lang-lZ6CYMWX.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5.21 kB 🔴 +4.63 kB
assets/WidgetInputNumber.vue_vue_type_script_setup_true_lang-DxlOH3rU.js (removed) 12.9 kB 🟢 -12.9 kB 🟢 -3.37 kB 🟢 -2.98 kB
assets/LazyImage.vue_vue_type_script_setup_true_lang-DfyPPq7c.js (new) 10.8 kB 🔴 +10.8 kB 🔴 +2.98 kB 🔴 +2.6 kB
assets/LazyImage.vue_vue_type_script_setup_true_lang-DYZmdfih.js (removed) 10.8 kB 🟢 -10.8 kB 🟢 -2.97 kB 🟢 -2.6 kB
assets/ComfyQueueButton-B5s2XvDB.js (new) 8.44 kB 🔴 +8.44 kB 🔴 +2.48 kB 🔴 +2.22 kB
assets/ComfyQueueButton-D90z2KPY.js (removed) 8.44 kB 🟢 -8.44 kB 🟢 -2.47 kB 🟢 -2.21 kB
assets/WidgetButton-BT4Lql5K.js (removed) 2.04 kB 🟢 -2.04 kB 🟢 -928 B 🟢 -812 B
assets/WidgetButton-Iv11yE1C.js (new) 2.04 kB 🔴 +2.04 kB 🔴 +927 B 🔴 +811 B
assets/WidgetLayoutField.vue_vue_type_script_setup_true_lang-Cj7vQINR.js (removed) 2 kB 🟢 -2 kB 🟢 -774 B 🟢 -671 B
assets/WidgetLayoutField.vue_vue_type_script_setup_true_lang-Btf8R7SA.js (new) 1.8 kB 🔴 +1.8 kB 🔴 +762 B 🔴 +673 B
assets/UserAvatar.vue_vue_type_script_setup_true_lang-CsUBtx3o.js (removed) 1.34 kB 🟢 -1.34 kB 🟢 -686 B 🟢 -594 B
assets/UserAvatar.vue_vue_type_script_setup_true_lang-YSMU82v2.js (new) 1.34 kB 🔴 +1.34 kB 🔴 +688 B 🔴 +594 B
assets/MediaTitle.vue_vue_type_script_setup_true_lang-DTUuL68C.js (removed) 897 B 🟢 -897 B 🟢 -502 B 🟢 -446 B
assets/MediaTitle.vue_vue_type_script_setup_true_lang-L9P-9ARs.js (new) 897 B 🔴 +897 B 🔴 +505 B 🔴 +463 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-blEzk3tS.js (new) 7.51 kB 🔴 +7.51 kB 🔴 +1.84 kB 🔴 +1.58 kB
assets/keybindingService-D-HvUPB9.js (removed) 7.51 kB 🟢 -7.51 kB 🟢 -1.83 kB 🟢 -1.58 kB
assets/serverConfigStore-BQ9GXEkd.js (removed) 2.83 kB 🟢 -2.83 kB 🟢 -909 B 🟢 -789 B
assets/serverConfigStore-DnGHxK_-.js (new) 2.83 kB 🔴 +2.83 kB 🔴 +909 B 🔴 +789 B
assets/audioService-Cr3psFmJ.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +963 B 🔴 +820 B
assets/audioService-D0dzHjGp.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -956 B 🟢 -815 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-CbaVyRGj.js (new) 1.41 kB 🔴 +1.41 kB 🔴 +651 B 🔴 +544 B
assets/audioUtils-CGXb8r8w.js (removed) 1.41 kB 🟢 -1.41 kB 🟢 -651 B 🟢 -548 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.6 MB (baseline 8.56 MB) • 🔴 +43.2 kB

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-other-Bcj5ZGrQ.js (new) 4.02 MB 🔴 +4.02 MB 🔴 +852 kB 🔴 +680 kB
assets/vendor-other-BawBdcAH.js (removed) 3.98 MB 🟢 -3.98 MB 🟢 -843 kB 🟢 -674 kB
assets/vendor-primevue-BTkNfp3w.js (removed) 1.96 MB 🟢 -1.96 MB 🟢 -336 kB 🟢 -202 kB
assets/vendor-primevue-BXp-qQ_a.js (new) 1.96 MB 🔴 +1.96 MB 🔴 +336 kB 🔴 +202 kB
assets/vendor-chart-3PqoxJsa.js (new) 452 kB 🔴 +452 kB 🔴 +99.1 kB 🔴 +81 kB
assets/vendor-chart-B_stLV7c.js (removed) 452 kB 🟢 -452 kB 🟢 -99.1 kB 🟢 -81 kB
assets/vendor-tiptap-CogkskC9.js (removed) 232 kB 🟢 -232 kB 🟢 -45.7 kB 🟢 -37.7 kB
assets/vendor-tiptap-Cq3IM7SP.js (new) 232 kB 🔴 +232 kB 🔴 +45.7 kB 🔴 +37.7 kB
assets/vendor-vue-1FHH2Qid.js (new) 160 kB 🔴 +160 kB 🔴 +37.3 kB 🔴 +31.6 kB
assets/vendor-vue-DCnqNLx6.js (removed) 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.85 MB (baseline 3.84 MB) • 🔴 +7.51 kB

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/WidgetRecordAudio-9IQWXBb-.js (removed) 20.4 kB 🟢 -20.4 kB 🟢 -5.23 kB 🟢 -4.63 kB
assets/WidgetRecordAudio-D9Bkb74Q.js (new) 20.4 kB 🔴 +20.4 kB 🔴 +5.24 kB 🔴 +4.63 kB
assets/AudioPreviewPlayer-Cj5o73OE.js (removed) 13.5 kB 🟢 -13.5 kB 🟢 -3.4 kB 🟢 -3.04 kB
assets/AudioPreviewPlayer-DgeGNwWD.js (new) 13.5 kB 🔴 +13.5 kB 🔴 +3.4 kB 🔴 +3.05 kB
assets/NumberControlPopover-BFbFOMdA.js (new) 7.51 kB 🔴 +7.51 kB 🔴 +2.15 kB 🔴 +1.91 kB
assets/WidgetGalleria-Bl0wcnWW.js (new) 4.1 kB 🔴 +4.1 kB 🔴 +1.45 kB 🔴 +1.3 kB
assets/WidgetGalleria-BvORDd6i.js (removed) 4.1 kB 🟢 -4.1 kB 🟢 -1.44 kB 🟢 -1.3 kB
assets/WidgetColorPicker-C24-8CB-.js (removed) 3.41 kB 🟢 -3.41 kB 🟢 -1.38 kB 🟢 -1.23 kB
assets/WidgetColorPicker-DnG4Nc4Z.js (new) 3.41 kB 🔴 +3.41 kB 🔴 +1.38 kB 🔴 +1.23 kB
assets/WidgetMarkdown-Bzjrn1Yw.js (new) 3.1 kB 🔴 +3.1 kB 🔴 +1.29 kB 🔴 +1.13 kB
assets/WidgetMarkdown-DKCKoP4e.js (removed) 3.1 kB 🟢 -3.1 kB 🟢 -1.28 kB 🟢 -1.13 kB
assets/WidgetAudioUI-BA0Q5r0T.js (new) 2.82 kB 🔴 +2.82 kB 🔴 +1.12 kB 🔴 +1.02 kB
assets/WidgetAudioUI-L6gVVTRd.js (removed) 2.82 kB 🟢 -2.82 kB 🟢 -1.12 kB 🟢 -1.02 kB
assets/MediaVideoTop-Ct23Baus.js (removed) 2.76 kB 🟢 -2.76 kB 🟢 -1.05 kB 🟢 -899 B
assets/MediaVideoTop-dIE4KFXj.js (new) 2.76 kB 🔴 +2.76 kB 🔴 +1.05 kB 🔴 +901 B
assets/WidgetChart-DID0Bx3W.js (removed) 2.48 kB 🟢 -2.48 kB 🟢 -932 B 🟢 -815 B
assets/WidgetChart-OY5ZJTC-.js (new) 2.48 kB 🔴 +2.48 kB 🔴 +933 B 🔴 +817 B
assets/WidgetTextarea-CXznHmJ7.js (new) 2.48 kB 🔴 +2.48 kB 🔴 +1.01 kB 🔴 +886 B
assets/WidgetTextarea-Dj_qqhgG.js (removed) 2.48 kB 🟢 -2.48 kB 🟢 -1.01 kB 🟢 -898 B
assets/WidgetImageCompare-B92Ab1oV.js (new) 2.21 kB 🔴 +2.21 kB 🔴 +748 B 🔴 +662 B
assets/WidgetImageCompare-Cddu857y.js (removed) 2.21 kB 🟢 -2.21 kB 🟢 -747 B 🟢 -664 B
assets/WidgetInputText-Bi-tPJS6.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +917 B 🔴 +849 B
assets/WidgetInputText-ZQ498wWq.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -917 B 🟢 -851 B
assets/MediaImageTop-CnUdyPjI.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +842 B 🔴 +715 B
assets/MediaImageTop-DBacjcjH.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -840 B 🟢 -716 B
assets/WidgetToggleSwitch-3K8macTa.js (removed) 1.58 kB 🟢 -1.58 kB 🟢 -759 B 🟢 -670 B
assets/WidgetToggleSwitch-Z7xwTO1_.js (new) 1.58 kB 🔴 +1.58 kB 🔴 +760 B 🔴 +666 B
assets/MediaImageBottom-CUe7uh1c.js (new) 1.57 kB 🔴 +1.57 kB 🔴 +746 B 🔴 +647 B
assets/MediaImageBottom-DMj2rZCb.js (removed) 1.57 kB 🟢 -1.57 kB 🟢 -741 B 🟢 -645 B
assets/MediaAudioBottom-BlqOgBtn.js (removed) 1.52 kB 🟢 -1.52 kB 🟢 -741 B 🟢 -651 B
assets/MediaAudioBottom-Z7wFLVwg.js (new) 1.52 kB 🔴 +1.52 kB 🔴 +747 B 🔴 +652 B
assets/MediaVideoBottom-D-W5hxG6.js (removed) 1.52 kB 🟢 -1.52 kB 🟢 -737 B 🟢 -652 B
assets/MediaVideoBottom-UPHGN5er.js (new) 1.52 kB 🔴 +1.52 kB 🔴 +741 B 🔴 +652 B
assets/Media3DBottom-ChwMyZgs.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +734 B 🔴 +646 B
assets/Media3DBottom-Dv7A9uEb.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -731 B 🟢 -645 B
assets/Media3DTop-BhTgxDMH.js (new) 1.49 kB 🔴 +1.49 kB 🔴 +767 B 🔴 +653 B
assets/Media3DTop-K3d7TRN2.js (removed) 1.49 kB 🟢 -1.49 kB 🟢 -763 B 🟢 -646 B
assets/MediaAudioTop-CSwk_z5a.js (removed) 1.46 kB 🟢 -1.46 kB 🟢 -738 B 🟢 -618 B
assets/MediaAudioTop-DOwKAyjz.js (new) 1.46 kB 🔴 +1.46 kB 🔴 +741 B 🔴 +617 B
assets/WidgetSelect-DTX1EDgL.js (new) 655 B 🔴 +655 B 🔴 +346 B 🔴 +292 B
assets/WidgetSelect-rjKnO2in.js (removed) 655 B 🟢 -655 B 🟢 -341 B 🟢 -287 B
assets/WidgetInputNumber-B6UwRcew.js (removed) 595 B 🟢 -595 B 🟢 -328 B 🟢 -271 B
assets/WidgetInputNumber-DPpMRoMx.js (new) 595 B 🔴 +595 B 🔴 +330 B 🔴 +277 B
assets/Load3D-BomNJnkU.js (new) 424 B 🔴 +424 B 🔴 +269 B 🔴 +223 B
assets/Load3D-DVLQIsZX.js (removed) 424 B 🟢 -424 B 🟢 -264 B 🟢 -223 B
assets/WidgetLegacy-CsjBvIIv.js (new) 364 B 🔴 +364 B 🔴 +238 B 🔴 +194 B
assets/WidgetLegacy-Cz2OKwqx.js (removed) 364 B 🟢 -364 B 🟢 -235 B 🟢 -230 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-5lOBdqcC.js 84.5 kB 84.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BOCuaVpE.js 73.4 kB 73.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-ClrEFGUz.js 72.4 kB 72.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Cw9RZWRY.js 89 B 89 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CyNU0iQX.js 99.3 kB 99.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D7gwLxft.js 114 kB 114 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DC8o4BCt.js 86.8 kB 86.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DKiesCV4.js 94.3 kB 94.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Hq2q-OtB.js 83.6 kB 83.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-USAlAlnj.js 82 kB 82 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-_Px5dSNW.js 306 kB 306 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-7z21KPoS.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-BWKZzBPK.js 346 kB 346 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CGbgH4Yl.js 320 kB 320 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CjjjdWkV.js 313 kB 313 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CVrNtxvj.js 288 kB 288 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DLRSA0IK.js 309 kB 309 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DQV2gnwA.js 372 kB 372 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-ofqLG5vz.js 310 kB 310 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
@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue (1)

70-75: Localize tooltip text via vue‑i18n instead of hard‑coding

buttonTooltip returns a hard‑coded, user‑visible English string, which conflicts with the “use vue‑i18n for ALL user‑facing strings” rule for src/**/components files.

Please move this into i18n, e.g.:

import { useI18n } from 'vue-i18n'

const { t } = useI18n()

const buttonTooltip = computed(() =>
  buttonsDisabled.value
    ? t('widget.numberInput.precisionLimitTooltip')
    : null
)

and add widget.numberInput.precisionLimitTooltip to src/locales/en/main.json.

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

297-315: Avoid as any in tests; widen component model type to accept undefined or use proper casting

The test intentionally exercises an undefined model value to validate graceful handling (line 63 of the component uses modelValue.value ?? 0 to handle falsy values). However, modelValue: undefined as any violates the "never use any / as any" guideline. Since the component's runtime code demonstrates it handles undefined defensibly:

Option 1 (preferred): Widen the component model type:

// In WidgetInputNumberInput.vue line 24
const modelValue = defineModel<number | undefined>({ default: 0 })

Then pass undefined without casting:

modelValue: undefined

Option 2 (if component changes aren't feasible): Avoid any:

-        modelValue: undefined as any
+        modelValue: undefined as unknown as number

Option 1 is cleaner since undefined is clearly a realistic runtime value the component already handles.

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

96-119: Remove debug console.log and unused template ref

The increment button still has a debug handler:

<NumberFieldIncrement
  v-if="!buttonsDisabled"
  :class="incrementClass"
  :disabled="!canIncrement"
  @mouseup="console.log('up')"
/>

and ref="numberFieldRoot" is declared but never read in the script.

  • Drop the console.log or replace it with the intended handler/emit.
  • If you don’t need to access the root instance, remove the ref to avoid confusion.

Example:

-      v-bind="filteredProps"
-      ref="numberFieldRoot"
+      v-bind="filteredProps"
@@
-        :disabled="!canIncrement"
-        @mouseup="console.log('up')"
+        :disabled="!canIncrement"
       />
🧹 Nitpick comments (7)
src/composables/graph/useGraphNodeManager.ts (1)

99-108: Good progress on the fix—use strict equality (===) to complete it.

The assignment operator (=) has been corrected to a comparison operator (==), which resolves the critical bug from the previous review. However, best practice is to use strict equality (===) instead of loose equality (==).

Apply this final refinement:

 function getControlWidget(widget: IBaseWidget): SafeControlWidget | undefined {
   const cagWidget = widget.linkedWidgets?.find(
-    (w) => w.name == 'control_after_generate'
+    (w) => w.name === 'control_after_generate'
   )
   if (!cagWidget) return
   return {
     value: validateControlWidgetValue(cagWidget.value),
     update: (value) => (cagWidget.value = validateControlWidgetValue(value))
   }
 }
tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts (4)

10-30: Mocks look good; consider asserting registry behavior or simplifying surface

The store and registry mocks are reasonable and match the composable’s dependencies. Right now, none of the tests assert numberControlRegistry.register / unregister, so either:

  • add expectations around registration/unregistration to verify lifecycle wiring, or
  • trim the mock to only what’s actually needed to keep the suite lean.

133-160: RANDOMIZE test never actually fails; prefer deterministic Math.random mocking

The loop from Line 149 to Line 159 is intended to detect lack of randomness, but it always ends with expect(true).toBe(true), so the test will pass even if the value never changes.

To make this non-flaky but meaningful, mock Math.random instead of relying on real randomness, e.g.:

-    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
-    })
+    it('should randomize value in RANDOMIZE mode', () => {
+      const modelValue = ref(100)
+      const options = { min: 10, max: 20, step: 1 }
+      const randomSpy = vi
+        .spyOn(Math, 'random')
+        .mockReturnValueOnce(0) // -> min
+        .mockReturnValueOnce(0.999999) // -> max
+
+      const { controlMode, applyControl } = useStepperControl(
+        modelValue,
+        options
+      )
+      controlMode.value = NumberControlMode.RANDOMIZE
+
+      applyControl()
+      expect(modelValue.value).toBe(10)
+
+      applyControl()
+      expect(modelValue.value).toBe(20)
+
+      randomSpy.mockRestore()
+    })

This keeps the test deterministic while actually validating the RANDOMIZE behavior. Based on learnings, tests should mock external dependencies rather than rely on real randomness.


176-188: Optional: also cover clamping when global seed exceeds max

You already test clamping when the global seed is below min. For completeness, consider adding a second test where mocked globalSeed is above max to assert the upper clamp path as well.


191-222: Consider adding coverage for step2 precedence over step

The composable uses step2 when present and falls back to step. The default-options tests are good, but they don’t currently assert this precedence. A small additional test like:

it('uses step2 when provided', () => {
  const modelValue = ref(0)
  const options = { step: 1, step2: 5 }
  const { controlMode, applyControl } = useStepperControl(modelValue, options)

  controlMode.value = NumberControlMode.INCREMENT
  applyControl()

  expect(modelValue.value).toBe(5)
})

would lock in that behavior.

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

77-85: Guard canIncrement / canDecrement when min / max are undefined

Right now:

const canDecrement = computed(() => modelValue.value > filteredProps.value.min)
const canIncrement = computed(() => modelValue.value < filteredProps.value.max)

If min or max are absent from filteredProps, these comparisons become value > undefined / value < undefined, which evaluate to false, so both buttons end up disabled even though there is no actual bound.

Consider treating missing bounds as unbounded, e.g.:

const canDecrement = computed(() => {
  const min = filteredProps.value.min
  if (typeof min !== 'number') return true
  return modelValue.value > min
})

const canIncrement = computed(() => {
  const max = filteredProps.value.max
  if (typeof max !== 'number') return true
  return modelValue.value < max
})

This more closely matches typical numeric input behavior when min/max are omitted.


123-129: Consider removing legacy .p-inputnumber-input styles

The scoped style still targets .p-inputnumber-input, which was specific to PrimeVue InputNumber. Now that the template uses NumberFieldRoot / NumberFieldInput from reka-ui, this selector is likely dead and can be removed unless you know those components still emit that class.

Cleaning it up will reduce confusion for future maintainers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 613fe12 and 0fb7466.

⛔ Files ignored due to path filters (18)
  • 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/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/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-default-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/nodeStates/lod.spec.ts-snapshots/vue-nodes-lod-inactive-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 (4)
  • src/composables/graph/useGraphNodeManager.ts (4 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts (11 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue (2 hunks)
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (20)
**/*.{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.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Use es-toolkit for utility functions

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.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/components/WidgetInputNumberInput.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
**/*.{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.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
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.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
src/**/*.ts

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

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

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts
  • src/composables/graph/useGraphNodeManager.ts
**/*.{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.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
**/*.{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.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.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/components/WidgetInputNumberInput.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
**/*.{test,spec}.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Unit and component tests should be located in tests-ui/ or co-located with components as src/components/**/*.{test,spec}.ts; E2E tests should be in browser_tests/

Files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
src/**/{services,composables}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

  • src/composables/graph/useGraphNodeManager.ts
**/*.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/**/*.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
**/*.{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
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
**/composables/use*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name composables in the format useXyz.ts

Files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
🧠 Learnings (18)
📚 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.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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} : Write tests for new features

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts
  • 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:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • 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:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 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.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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} : Follow existing test patterns in the codebase

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 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.test.ts
  • 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 : Replace PrimeVue Dropdown component with Select

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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: Check tests-ui/README.md for test guidelines

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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} : Mocks should be cleanly written and easy to understand, with reusable mocks where possible

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.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/**/*.{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 InlineMessage component with Message

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 : Replace PrimeVue Calendar component with DatePicker

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 : Replace PrimeVue Chips component with AutoComplete with multiple enabled

Applied to files:

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

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
🧬 Code graph analysis (1)
tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts (2)
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (1)
  • useStepperControl (39-96)
src/renderer/core/layout/store/layoutStore.ts (1)
  • onChange (922-925)
🪛 ESLint
src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.test.ts

[error] 7-7: Unable to resolve path to module 'reka-ui'.

(import-x/no-unresolved)

src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue

[error] 7-7: Unable to resolve path to module 'reka-ui'.

(import-x/no-unresolved)

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

[error] 1-1: Resolve error: EACCES: permission denied, open '/LWRRXZfkEC'
at Object.writeFileSync (node:fs:2409:20)
at l (file:///home/jailuser/git/node_modules/.pnpm/[email protected]/node_modules/get-tsconfig/dist/index.mjs:7:13670)
at createFilesMatcher (file:///home/jailuser/git/node_modules/.pnpm/[email protected]/node_modules/get-tsconfig/dist/index.mjs:7:14422)
at resolve (file:///home/jailuser/git/node_modules/.pnpm/[email protected][email protected]_@typescript-eslin_da4796079dab5a32abf73f9910d12370/node_modules/eslint-import-resolver-typescript/lib/index.js:70:65)
at Object.resolve (file:///home/jailuser/git/node_modules/.pnpm/[email protected][email protected]_@typescript-eslin_da4796079dab5a32abf73f9910d12370/node_modules/eslint-import-resolver-typescript/lib/index.js:147:20)
at file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:170:69
at setRuleContext (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint-import-context/lib/index.js:23:20)
at fullResolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:170:30)
at relative (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:215:12)
at resolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:220:16)
at ExportMap.get (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/export-map.js:88:22)
at processBodyStatement (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/rules/namespace.js:9:31)
at Program (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/rules/namespace.js:100:21)
at ruleErrorHandler (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:1173:33)
at /home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/source-code-visitor.js:76:46
at Array.forEach ()
at SourceCodeVisitor.callSync (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/source-code-visitor.js:76:30)
at /home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/source-code-traverser.js:291:18
at Array.forEach ()
at SourceCodeTraverser.traverseSync (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/source-code-traverser.js:290:10)
at runRules (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:1214:12)
at #flatVerifyWithoutProcessors (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:2101:4)
at Linter._verifyWithFlatConfigArrayAndWithoutProcessors (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:2189:43)
at Linter._verifyWithFlatConfigArray (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:2292:15)
at Linter.verify (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:1677:10)
at Linter.verifyAndFix (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:2557:20)
at verifyText (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/eslint/eslint-helpers.js:1179:45)
at readAndVerifyFile (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/eslint/eslint-helpers.js:1320:10)

(import-x/namespace)


[error] 1-1: Resolve error: EACCES: permission denied, open '/sTejqfgbMS'
at Object.writeFileSync (node:fs:2409:20)
at l (file:///home/jailuser/git/node_modules/.pnpm/[email protected]/node_modules/get-tsconfig/dist/index.mjs:7:13670)
at createFilesMatcher (file:///home/jailuser/git/node_modules/.pnpm/[email protected]/node_modules/get-tsconfig/dist/index.mjs:7:14422)
at resolve (file:///home/jailuser/git/node_modules/.pnpm/[email protected][email protected]_@typescript-eslin_da4796079dab5a32abf73f9910d12370/node_modules/eslint-import-resolver-typescript/lib/index.js:70:65)
at Object.resolve (file:///home/jailuser/git/node_modules/.pnpm/[email protected][email protected]_@typescript-eslin_da4796079dab5a32abf73f9910d12370/node_modules/eslint-import-resolver-typescript/lib/index.js:147:20)
at file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:170:69
at setRuleContext (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint-import-context/lib/index.js:23:20)
at fullResolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:170:30)
at relative (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:215:12)
at resolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:220:16)
at checkSourceValue (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/rules/no-unresolved.js:31:34)
at checkSourceValue (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/module-visitor.js:14:9)
at checkSource (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/module-visitor.js:17:9)
at ruleErrorHandler (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:1173:33)
at /home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/source-code-visitor.js:76:46
at Array.forEach ()
at SourceCodeVisitor.callSync (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/source-code-visitor.js:76:30)
at /home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/source-code-traverser.js:291:18
at Array.forEach ()
at SourceCodeTraverser.traverseSync (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/source-code-traverser.js:290:10)
at runRules (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:1214:12)
at #flatVerifyWithoutProcessors (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:2101:4)
at Linter._verifyWithFlatConfigArrayAndWithoutProcessors (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:2189:43)
at Linter._verifyWithFlatConfigArray (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:2292:15)
at Linter.verify (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:1677:10)
at Linter.verifyAndFix (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:2557:20)
at verifyText (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/eslint/eslint-helpers.js:1179:45)
at readAndVerifyFile (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/eslint/eslint-helpers.js:1320:10)

(import-x/no-unresolved)


[error] 1-1: Resolve error: EACCES: permission denied, open '/oJPDLzInWr'
at Object.writeFileSync (node:fs:2409:20)
at l (file:///home/jailuser/git/node_modules/.pnpm/[email protected]/node_modules/get-tsconfig/dist/index.mjs:7:13670)
at createFilesMatcher (file:///home/jailuser/git/node_modules/.pnpm/[email protected]/node_modules/get-tsconfig/dist/index.mjs:7:14422)
at resolve (file:///home/jailuser/git/node_modules/.pnpm/[email protected][email protected]_@typescript-eslin_da4796079dab5a32abf73f9910d12370/node_modules/eslint-import-resolver-typescript/lib/index.js:70:65)
at Object.resolve (file:///home/jailuser/git/node_modules/.pnpm/[email protected][email protected]_@typescript-eslin_da4796079dab5a32abf73f9910d12370/node_modules/eslint-import-resolver-typescript/lib/index.js:147:20)
at file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:170:69
at setRuleContext (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint-import-context/lib/index.js:23:20)
at fullResolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:170:30)
at relative (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:215:12)
at resolve (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/resolve.js:220:16)
at importType (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/import-type.js:126:63)
at checkImportForRelativePackage (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/rules/no-relative-packages.js:15:38)
at file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/rules/no-relative-packages.js:59:40
at checkSourceValue (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/module-visitor.js:14:9)
at checkSource (file:///home/jailuser/git/node_modules/.pnpm/eslint-plugin-import-x@4.16.1_@typescript-eslint[email protected]_eslint@[email protected]__d4b6b79e6f12f59d34d55ebbf27dc73f/node_modules/eslint-plugin-import-x/lib/utils/module-visitor.js:17:9)
at ruleErrorHandler (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:1173:33)
at /home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/source-code-visitor.js:76:46
at Array.forEach ()
at SourceCodeVisitor.callSync (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/source-code-visitor.js:76:30)
at /home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/source-code-traverser.js:291:18
at Array.forEach ()
at SourceCodeTraverser.traverseSync (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/source-code-traverser.js:290:10)
at runRules (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:1214:12)
at #flatVerifyWithoutProcessors (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:2101:4)
at Linter._verifyWithFlatConfigArrayAndWithoutProcessors (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:2189:43)
at Linter._verifyWithFlatConfigArray (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:2292:15)
at Linter.verify (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:1677:10)
at Linter.verifyAndFix (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/linter/linter.js:2557:20)
at verifyText (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/eslint/eslint-helpers.js:1179:45)
at readAndVerifyFile (/home/jailuser/git/node_modules/.pnpm/[email protected][email protected]/node_modules/eslint/lib/eslint/eslint-helpers.js:1320:10)

(import-x/no-relative-packages)


[error] 1-1: Unable to resolve path to module 'pinia'.

(import-x/no-unresolved)


[error] 2-2: Unable to resolve path to module 'vitest'.

(import-x/no-unresolved)


[error] 3-3: Unable to resolve path to module 'vue'.

(import-x/no-unresolved)


[error] 8-8: Unable to resolve path to module '@/renderer/extensions/vueNodes/widgets/composables/useStepperControl'.

(import-x/no-unresolved)

🔇 Additional comments (6)
src/composables/graph/useGraphNodeManager.ts (2)

142-143: LGTM: Clean integration of control widget support.

The control widget is correctly wired into the safe widget data extraction flow, following the same pattern as isDOMWidget. The optional nature of controlWidget is properly handled.


87-98: The review comment contains a factual error: 'link_to_global' is not a valid ControlWidgetOptions value.

The ControlWidgetOptions type definition contains only four values: 'fixed', 'increment', 'decrement', and 'randomize'. The suggested diff adding a case for 'link_to_global' would violate TypeScript type safety and fail compilation.

The current implementation is correct. The function:

  • Explicitly handles the three most common cases ('fixed', 'increment', 'decrement')
  • Defaults to 'randomize' for any unrecognized value
  • Returns a valid ControlWidgetOptions type in all paths

Minor improvements could include making 'randomize' an explicit case and adding a warning for truly invalid values, but the core suggestion in the review is invalid.

Likely an incorrect or invalid review comment.

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

32-60: Solid initialization coverage

These tests cleanly verify the default mode (RANDOMIZE) and that applyControl is exposed, which aligns with the composable API.


224-271: Good coverage of onChange vs direct assignment paths

These tests nicely exercise both the callback path and the direct assignment fallback, and the FIXED-mode suppression of onChange, which matches the composable’s switch logic.

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

2-7: I'll help you verify the review comment for the WidgetInputNumberInput.vue file. Let me start by checking the reka-ui dependency and examining the component file.
<function_calls>


#!/bin/bash

Verify dependency declaration

echo "=== Checking package.json for reka-ui ==="
cat package.json | jq '.dependencies["reka-ui"], .devDependencies["reka-ui"]'

echo ""
echo "=== Confirming reka-ui references in codebase ==="
rg -n "from 'reka-ui'" -S


</function_calls>

Now let me examine the full WidgetInputNumberInput.vue file to check for the other issues mentioned in the scratchpad:
<function_calls>


#!/bin/bash

Find and examine the WidgetInputNumberInput.vue file

echo "=== Finding WidgetInputNumberInput.vue ==="
fd -type f "WidgetInputNumberInput.vue"

echo ""
echo "=== Full content of WidgetInputNumberInput.vue ==="
cat -n src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue


</function_calls>

<function_calls>


#!/bin/bash

Check for i18n usage and console.log/debug in the file

echo "=== Checking for i18n and console patterns ==="
rg -n "buttonTooltip|console.|i18n|useI18n" src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue


</function_calls>

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

3-7: Reka-ui is a legitimate TypeScript-supported package; verify it's in your project dependencies.

Reka-ui is a published npm package providing unstyled Vue UI primitives with TypeScript support. The imports (NumberFieldRoot, NumberFieldIncrement, NumberFieldDecrement) follow the library's standard component export pattern.

The ESLint import-x/no-unresolved warning suggests either:

  • reka-ui is missing from dependencies/devDependencies in package.json
  • The unplugin resolver (reka-ui/resolver) is not configured in your ESLint/TypeScript config
  • A configuration issue preventing import resolution

Confirm that reka-ui is declared in your package.json and that your ESLint/TypeScript tooling is properly configured to resolve it. If the package is present but ESLint still warns, check for missing resolver configuration.

@DrJKL DrJKL added the claude-review Add to trigger a PR code review from Claude Code label Nov 27, 2025
@lostdisc
Copy link

either implement or remove the config button functionality - think it should open settings?

Personally as a random SDXL user, I do think the "control before generate" setting should be made a lot more obvious (or better yet, the default), since it's more intuitive for users wanting to quickly retry a seed. In before-mode, when I get a promising-but-not-perfect result from a random seed, I can just switch to "fixed" mode, tweak my prompt, and rerun.

In after-mode, by contrast, the seed has already changed, and I have to retrieve the old value by digging into the image's metadata in a separate program and manually copy-pasting it back into the seed field. I can't even copy seeds from the job history, only a useless* job ID.

*: I'm sure it's useful for something, just like control-after-generate must be useful for something, but I can't fathom why after-mode is the default. So I'm glad there's a before/after setting, but I wish its existence and lifesaving practical effect were made more obvious.

@christian-byrne
Copy link
Contributor

@AustinMroz I can fix these merge conflicts right now, let me know if you are currently also doing this and I'll avoid pushing. If not, please ignore.

@christian-byrne
Copy link
Contributor

either implement or remove the config button functionality - think it should open settings?

Personally as a random SDXL user, I do think the "control before generate" setting should be made a lot more obvious (or better yet, the default), since it's more intuitive for users wanting to quickly retry a seed. In before-mode, when I get a promising-but-not-perfect result from a random seed, I can just switch to "fixed" mode, tweak my prompt, and rerun.

In after-mode, by contrast, the seed has already changed, and I have to retrieve the old value by digging into the image's metadata in a separate program and manually copy-pasting it back into the seed field. I can't even copy seeds from the job history, only a useless* job ID.

*: I'm sure it's useful for something, just like control-after-generate must be useful for something, but I can't fathom why after-mode is the default. So I'm glad there's a before/after setting, but I wish its existence and lifesaving practical effect were made more obvious.

This is great feedback, what would you say would be most intuitive or obvious to you?

@lostdisc
Copy link

I think "control before generate" would be the more intuitive default. With "control after generate", it's confusing for the seed to change at the end of a gen, since it no longer corresponds with the output on display, and there's no way within ComfyUI to retrieve the old seed for a retry with different prompts/settings, which is a common use case.

The one downside I can think of for "control before generate" is that increment/decrement would also change the starting seed for the first gen, which would be unexpected. Maybe it can skip changing the seed when the seed value has just been manually changed by the user. (This exception would also make sense for randomize mode.)

@christian-byrne christian-byrne added the New Browser Test Expectations New browser test screenshot should be set by github action label Nov 30, 2025
@github-actions github-actions bot removed the New Browser Test Expectations New browser test screenshot should be set by github action label Nov 30, 2025
@christian-byrne
Copy link
Contributor

Cleaning a few things up

@DrJKL
Copy link
Contributor

DrJKL commented Dec 1, 2025

I think "control before generate" would be the more intuitive default. With "control after generate", it's confusing for the seed to change at the end of a gen, since it no longer corresponds with the output on display, and there's no way within ComfyUI to retrieve the old seed for a retry with different prompts/settings, which is a common use case.

The one downside I can think of for "control before generate" is that increment/decrement would also change the starting seed for the first gen, which would be unexpected. Maybe it can skip changing the seed when the seed value has just been manually changed by the user. (This exception would also make sense for randomize mode.)

There is an option in the settings to change it to control before generate.
image

Without these, randomized values can exceed the maximum safe integer
range which results in the increment and decrement buttons being 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: 2

🧹 Nitpick comments (2)
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (2)

50-51: Magic number should be a named constant.

The value 1125899906842624 (2^50) is used to define safe bounds but is not documented. This should be extracted to a named constant with a comment explaining why this specific bound was chosen instead of JavaScript's Number.MAX_SAFE_INTEGER (2^53 - 1).

Apply this diff to improve maintainability:

+// Safe bounds for numeric controls to prevent precision loss
+// Uses 2^50 instead of MAX_SAFE_INTEGER for [reason - e.g., ComfyUI backend limitation]
+const SAFE_NUMERIC_BOUND = 1125899906842624
+
 export function useStepperControl(
   modelValue: Ref<number>,
   options: StepperControlOptions,
   defaultValue?: ControlWidgetOptions
 ) {
   const controlMode = ref<NumberControlMode>(convertToEnum(defaultValue))
   const controlId = Symbol('numberControl')
   const globalSeedStore = useGlobalSeedStore()
 
   const applyControl = () => {
     const { min = 0, max = 1000000, step2, step = 1, onChange } = options
-    const safeMax = Math.min(1125899906842624, max)
-    const safeMin = Math.max(-1125899906842624, min)
+    const safeMax = Math.min(SAFE_NUMERIC_BOUND, max)
+    const safeMin = Math.max(-SAFE_NUMERIC_BOUND, min)

49-49: Consider validating min ≤ max constraint.

The code destructures min and max with defaults but does not validate that min ≤ max. If a caller provides min > max, the behavior could be unexpected (e.g., safeMin > safeMax, making clamping logic produce incorrect results).

Consider adding a defensive check:

   const applyControl = () => {
     const { min = 0, max = 1000000, step2, step = 1, onChange } = options
+    if (min > max) {
+      console.warn('useStepperControl: min should not exceed max', { min, max })
+      return
+    }
     const safeMax = Math.min(SAFE_NUMERIC_BOUND, max)
     const safeMin = Math.max(-SAFE_NUMERIC_BOUND, min)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95b95ed and 7dd2f52.

⛔ Files ignored due to path filters (17)
  • 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/colors.spec.ts-snapshots/vue-node-custom-colors-light-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)
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{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/composables/useStepperControl.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Use es-toolkit for utility functions

Files:

  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.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
**/*.{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/composables/useStepperControl.ts
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/composables/useStepperControl.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
**/*.{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/composables/useStepperControl.ts
**/*.{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/composables/useStepperControl.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/**/{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/**/*.{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/composables/useStepperControl.ts
src/**/{composables,components}/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Clean up subscriptions in state management to prevent memory leaks

Files:

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

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

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

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.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 (1)
📚 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/composables/useStepperControl.ts
🔇 Additional comments (4)
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (4)

1-15: LGTM! Enum and imports are well-structured.

The imports follow Vue 3 Composition API conventions, and the enum correctly uses camelCase naming.


17-23: LGTM! Interface is properly typed.

The interface follows TypeScript best practices with clear, optional properties.


66-68: Verify: Does RANDOMIZE mode need to respect the step increment?

The random value generation in RANDOMIZE mode produces any integer within [safeMin, safeMax] but does not align to step increments. For example, with min=0, max=100, step=10, this could generate 47 instead of multiples of 10 like 0, 10, 20, ..., 100.

Is this intentional, or should random values be step-aligned?

If step-aligned randomization is desired, consider this approach:

       case NumberControlMode.RANDOMIZE:
-        newValue = Math.floor(Math.random() * (safeMax - safeMin + 1)) + safeMin
+        {
+          const range = safeMax - safeMin
+          const steps = Math.floor(range / actualStep)
+          const randomStep = Math.floor(Math.random() * (steps + 1))
+          newValue = safeMin + randomStep * actualStep
+        }
         break

84-98: LGTM! Proper lifecycle management and cleanup.

The composable correctly registers with the numberControlRegistry on mount and unregisters on unmount, preventing memory leaks. The return values are appropriate for the composable pattern.

Comment on lines +25 to +37
function convertToEnum(str?: ControlWidgetOptions): NumberControlMode {
switch (str) {
case 'fixed':
return NumberControlMode.FIXED
case 'increment':
return NumberControlMode.INCREMENT
case 'decrement':
return NumberControlMode.DECREMENT
case 'randomize':
return NumberControlMode.RANDOMIZE
}
return NumberControlMode.RANDOMIZE
}
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 case for 'linkToGlobal' in convertToEnum.

The function handles 'fixed', 'increment', 'decrement', and 'randomize', but does not handle 'linkToGlobal'. If a user passes 'linkToGlobal' as the defaultValue, it will incorrectly fall through to the default case and return RANDOMIZE instead of LINK_TO_GLOBAL.

Apply this diff to add the missing case:

 function convertToEnum(str?: ControlWidgetOptions): 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
 }
🤖 Prompt for AI Agents
In src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
around lines 25 to 37, the convertToEnum function is missing a case for
'linkToGlobal' so that value falls through to RANDOMIZE; add a case clause for
'linkToGlobal' that returns NumberControlMode.LINK_TO_GLOBAL (keeping the
existing default return as RANDOMIZE) so the function maps that string to the
correct enum.

break
case NumberControlMode.LINK_TO_GLOBAL:
// Use global seed value, constrained by min/max
newValue = Math.max(min, Math.min(safeMax, globalSeedStore.globalSeed))
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

Inconsistent boundary constraint in LINK_TO_GLOBAL mode.

Line 71 uses min for the lower bound while using safeMax for the upper bound. All other control modes consistently use safeMin and safeMax. 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
📝 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
newValue = Math.max(min, Math.min(safeMax, globalSeedStore.globalSeed))
case NumberControlMode.LINK_TO_GLOBAL:
// Use global seed value, constrained by min/max
newValue = Math.max(safeMin, Math.min(safeMax, globalSeedStore.globalSeed))
break
🤖 Prompt for AI Agents
In src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
around line 71, the LINK_TO_GLOBAL branch uses `min` for the lower bound but
should use `safeMin` to match other modes; update the expression `newValue =
Math.max(min, Math.min(safeMax, globalSeedStore.globalSeed))` to use `safeMin`
instead of `min`, ensuring the code consistently constrains values with
`safeMin` and `safeMax`.

@lostdisc
Copy link

lostdisc commented Dec 1, 2025

There is an option in the settings to change it to control before generate.

I know; that's how I can articulate the concept now. But until recently, I was just suffering through the default mode with no idea it could be changed, until someone else pointed out this setting existed and did something useful. I bet many users follow the same path, or never find out.

I think putting a toggle right in the KSampler node would help a lot with discoverability, but since it's a shared setting, maybe it's hard to keep the UI state synced across multiple nodes?

Barring that, maybe the simplest improvement would be changing the terminology from "control before/after generate" to "update seed before/after generate", which makes the practical effect much clearer. Similarly, the setting could be relabeled from "widget control mode: before/after" to "update seed fields: before generate / after generate".

(Or is the current wording a vague "control" because it also affects things other than seed RNGs?)

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 size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants