Skip to content

Conversation

@AustinMroz
Copy link
Collaborator

@AustinMroz AustinMroz commented Nov 27, 2025

Continuation of #6034 with

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

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

image

┆Issue is synchronized with this Notion page by Unito

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

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

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

coderabbitai bot commented Nov 27, 2025

📝 Walkthrough

Walkthrough

This pull request introduces a number control widget system that enables managing number inputs with multiple control modes (fixed, increment, decrement, randomize, link-to-global). It adds composables, services, UI components, a global seed store, and integrates control execution into the queue prompt flow with comprehensive test coverage and localization.

Changes

Cohort / File(s) Summary
Type System & Control Widget Infrastructure
src/types/simplifiedWidget.ts, src/composables/graph/useGraphNodeManager.ts
Added ControlOptions type, SafeControlWidget interface with value and update method, validateControlOption validator. Extended SafeWidgetData to include optional controlWidget field and added helper getControlWidget() to derive control widgets from linked widgets.
Global State Management
src/stores/globalSeedStore.ts
New Pinia store useGlobalSeedStore with reactive globalSeed (initialized 0–999999) and setGlobalSeed(value) setter.
Core Services & Registry
src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
New NumberControlRegistry class providing deterministic registry for control callbacks with register(), unregister(), executeControls(phase) methods; exported singleton numberControlRegistry and executeNumberControls(phase) function. Execution gated by Comfy.WidgetControlMode setting.
Control Composables
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts, useNumberStepCalculation.ts
Added useStepperControl hook managing control modes (FIXED, INCREMENT, DECREMENT, RANDOMIZE, LINK_TO_GLOBAL) with step application and bounds clamping; mounts/unmounts callbacks with registry. Added useNumberStepCalculation composable centralizing step calculation logic for number inputs.
Control UI Components
src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue, WidgetInputNumberWithControl.vue
New NumberControlPopover component rendering mode selection popover with ToggleSwitch options and settings dialog. New WidgetInputNumberWithControl component composing number input with control button and async popover, using useStepperControl to manage state.
Number Input Components
src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue, WidgetInputNumberInput.vue, WidgetInputNumberSlider.vue
Updated WidgetInputNumber to conditionally render WidgetInputNumberWithControl when controlWidget present. Updated WidgetInputNumberInput to support slot rendering with conditional right-padding. Updated WidgetInputNumberSlider to use useNumberStepCalculation and allow 0 as valid value.
Widget Creation Composables
src/renderer/extensions/vueNodes/widgets/composables/useIntWidget.ts, useFloatWidget.ts, useComboWidget.ts
Enhanced widget creation to conditionally link control widgets via addValueControlWidget() when control_after_generate is true; refactored variable naming and import ordering.
Component Payload Extension
src/renderer/extensions/vueNodes/components/NodeWidgets.vue
Added controlWidget field to simplified widget payload alongside existing spec.
Select Dropdown Adjustments
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
Removed assetData-based early return in allItems; now always concatenates inputItems and outputItems.
Event Handling Update
src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
Removed pointerup.stop directive; retained pointerdown.stop and pointermove.stop.
App Integration
src/scripts/app.ts
Added calls to executeNumberControls('before') and executeNumberControls('after') around pre- and post-queue widget callback stages in queuePrompt flow.
Utilities
src/scripts/utils.ts
Removed JSDoc @knipIgnoreUnusedButUsedByCustomNodes tags. Added browser-only dynamic import for downloadUtil with graceful fallback.
Localization
src/locales/en/main.json
Added widgets.numberControl block with keys for header (prefix, after, before, postfix) and control actions (linkToGlobal, randomize, increment, decrement, editSettings) with descriptions.
Unit Tests
tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts, useWidgetRenderer.test.ts, tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts, tests-ui/tests/stores/globalSeedStore.test.ts
Added comprehensive test suites for useStepperControl, NumberControlRegistry, and useGlobalSeedStore; updated useWidgetRenderer tests with Pinia setup and settings store mocks.
Browser Test Updates
browser_tests/fixtures/VueNodeHelpers.ts, browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts
Updated getInputNumberControls to use nth(1) for decrement button. Updated seed widget retrieval to use explicit .first() selector.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as NumberInput +<br/>ControlButton
    participant Popover as NumberControlPopover
    participant Stepper as useStepperControl
    participant Registry as NumberControlRegistry
    participant App as app.queuePrompt()
    participant Store as globalSeedStore

    User->>UI: Click control button
    activate UI
    UI->>Popover: toggle()
    activate Popover
    Popover->>User: Show mode options
    deactivate UI

    User->>Popover: Select mode<br/>(increment/randomize/etc)
    Popover->>Stepper: emit update:controlMode
    deactivate Popover
    activate Stepper
    Stepper->>Stepper: setControlMode(mode)
    Stepper->>UI: widget.controlWidget.update(mode)
    deactivate Stepper

    User->>App: Queue prompt
    activate App
    App->>Registry: executeNumberControls('before')
    activate Registry
    Registry->>Registry: Get all registered<br/>applyControl callbacks
    Registry->>Stepper: applyControl()
    activate Stepper
    alt mode = RANDOMIZE
        Stepper->>Stepper: value = random(min, max)
    else mode = INCREMENT
        Stepper->>Stepper: value = current + step
    else mode = LINK_TO_GLOBAL
        Stepper->>Store: Get globalSeed
        activate Store
        Store-->>Stepper: seed value
        deactivate Store
        Stepper->>Stepper: value = clamp(seed, min, max)
    else mode = FIXED
        Stepper->>Stepper: (no change)
    end
    Stepper->>UI: onChange(newValue)
    deactivate Stepper
    deactivate Registry
    App->>App: Process queued prompt
    App->>Registry: executeNumberControls('after')
    deactivate App
Loading
✨ 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

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

❤️ Share

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

@github-actions
Copy link

github-actions bot commented Nov 27, 2025

🎨 Storybook Build Status

Build completed successfully!

⏰ Completed at: 12/05/2025, 09:38:31 PM UTC

🔗 Links


🎉 Your Storybook is ready for review!

@github-actions
Copy link

github-actions bot commented Nov 27, 2025

🎭 Playwright Test Results

⚠️ Tests passed with flaky tests

⏰ Completed at: 12/05/2025, 09:46:54 PM UTC

📈 Summary

  • Total Tests: 495
  • Passed: 482 ✅
  • Failed: 0
  • Flaky: 3 ⚠️
  • Skipped: 10 ⏭️

📊 Test Reports by Browser

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

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

@github-actions
Copy link

github-actions bot commented Nov 27, 2025

Bundle Size Report

Summary

  • Raw size: 17.1 MB baseline 17 MB — 🔴 +17.8 kB
  • Gzip: 3.38 MB baseline 3.38 MB — 🔴 +4.76 kB
  • Brotli: 2.59 MB baseline 2.59 MB — 🔴 +4.06 kB
  • Bundles: 98 current • 97 baseline • 40 added / 39 removed

Category Glance
Other 🔴 +7.49 kB (3.82 MB) · UI Components 🔴 +6.88 kB (180 kB) · App Entry Points 🔴 +2.75 kB (3.2 MB) · Graph Workspace 🔴 +684 B (975 kB) · Vendor & Third-Party ⚪ 0 B (8.56 MB) · Panels & Settings ⚪ 0 B (298 kB) · + 3 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-C0-05-Cp.js (new) 2.98 MB 🔴 +2.98 MB 🔴 +620 kB 🔴 +471 kB
assets/index-D9tpAyBT.js (removed) 2.97 MB 🟢 -2.97 MB 🟢 -619 kB 🟢 -470 kB
assets/index-BCitbpcs.js (new) 223 kB 🔴 +223 kB 🔴 +47.6 kB 🔴 +39.3 kB
assets/index-DCCqJ9vJ.js (removed) 223 kB 🟢 -223 kB 🟢 -47.6 kB 🟢 -39.3 kB
assets/index-B7UpMnpI.js (removed) 345 B 🟢 -345 B 🟢 -244 B 🟢 -235 B
assets/index-D77rFosg.js (new) 345 B 🔴 +345 B 🔴 +244 B 🔴 +235 B

Status: 3 added / 3 removed

Graph Workspace — 975 kB (baseline 975 kB) • 🔴 +684 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-DGNW18BQ.js (new) 975 kB 🔴 +975 kB 🔴 +188 kB 🔴 +144 kB
assets/GraphView-Bszml-ls.js (removed) 975 kB 🟢 -975 kB 🟢 -188 kB 🟢 -144 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

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

Status: 1 added / 1 removed

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CreditsPanel-BDiEvRYK.js (removed) 21.4 kB 🟢 -21.4 kB 🟢 -5.15 kB 🟢 -4.5 kB
assets/CreditsPanel-DokuShJe.js (new) 21.4 kB 🔴 +21.4 kB 🔴 +5.14 kB 🔴 +4.49 kB
assets/KeybindingPanel-cnFNBLYK.js (new) 13.6 kB 🔴 +13.6 kB 🔴 +3.42 kB 🔴 +3.02 kB
assets/KeybindingPanel-DgBPNc84.js (removed) 13.6 kB 🟢 -13.6 kB 🟢 -3.42 kB 🟢 -3.01 kB
assets/ExtensionPanel-BpEuZ7Mo.js (new) 10.8 kB 🔴 +10.8 kB 🔴 +2.57 kB 🔴 +2.25 kB
assets/ExtensionPanel-DGoY3lTU.js (removed) 10.8 kB 🟢 -10.8 kB 🟢 -2.57 kB 🟢 -2.26 kB
assets/AboutPanel-C0IybpFb.js (removed) 9.16 kB 🟢 -9.16 kB 🟢 -2.46 kB 🟢 -2.21 kB
assets/AboutPanel-h5Cp18jG.js (new) 9.16 kB 🔴 +9.16 kB 🔴 +2.46 kB 🔴 +2.21 kB
assets/ServerConfigPanel-BDkdXUVH.js (new) 6.56 kB 🔴 +6.56 kB 🔴 +1.83 kB 🔴 +1.62 kB
assets/ServerConfigPanel-CzUotMmV.js (removed) 6.56 kB 🟢 -6.56 kB 🟢 -1.83 kB 🟢 -1.63 kB
assets/UserPanel-CEURreXS.js (removed) 6.23 kB 🟢 -6.23 kB 🟢 -1.72 kB 🟢 -1.51 kB
assets/UserPanel-Dl8T_xdL.js (new) 6.23 kB 🔴 +6.23 kB 🔴 +1.72 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 — 180 kB (baseline 173 kB) • 🔴 +6.88 kB

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/Load3D.vue_vue_type_script_setup_true_lang-1j_vm3j2.js (removed) 53.9 kB 🟢 -53.9 kB 🟢 -8.52 kB 🟢 -7.3 kB
assets/Load3D.vue_vue_type_script_setup_true_lang-DEATPNFA.js (new) 53.9 kB 🔴 +53.9 kB 🔴 +8.52 kB 🔴 +7.31 kB
assets/WidgetSelect.vue_vue_type_script_setup_true_lang-Cvc1b9II.js (removed) 47.6 kB 🟢 -47.6 kB 🟢 -10.3 kB 🟢 -8.9 kB
assets/WidgetSelect.vue_vue_type_script_setup_true_lang-DNb1CQ5F.js (new) 47.6 kB 🔴 +47.6 kB 🔴 +10.2 kB 🔴 +8.89 kB
assets/LazyImage.vue_vue_type_script_setup_true_lang-BgvV_qeM.js (removed) 43.8 kB 🟢 -43.8 kB 🟢 -9.78 kB 🟢 -8.55 kB
assets/LazyImage.vue_vue_type_script_setup_true_lang-Dc8G5WF-.js (new) 43.8 kB 🔴 +43.8 kB 🔴 +9.78 kB 🔴 +8.56 kB
assets/WidgetInputNumber.vue_vue_type_script_setup_true_lang-iMqvoz8J.js (new) 20.1 kB 🔴 +20.1 kB 🔴 +5.13 kB 🔴 +4.56 kB
assets/WidgetInputNumber.vue_vue_type_script_setup_true_lang-O4ha8Shj.js (removed) 12.9 kB 🟢 -12.9 kB 🟢 -3.37 kB 🟢 -2.96 kB
assets/ComfyQueueButton-aDiGWPmJ.js (removed) 8.44 kB 🟢 -8.44 kB 🟢 -2.47 kB 🟢 -2.21 kB
assets/ComfyQueueButton-B4yQDJVT.js (new) 8.44 kB 🔴 +8.44 kB 🔴 +2.47 kB 🔴 +2.21 kB
assets/WidgetLayoutField.vue_vue_type_script_setup_true_lang-Bb3LZ5y7.js (removed) 2.26 kB 🟢 -2.26 kB 🟢 -876 B 🟢 -759 B
assets/WidgetLayoutField.vue_vue_type_script_setup_true_lang-2aDK9bdc.js (new) 2.07 kB 🔴 +2.07 kB 🔴 +863 B 🔴 +748 B
assets/MediaTitle.vue_vue_type_script_setup_true_lang-BGfegHFu.js (new) 897 B 🔴 +897 B 🔴 +502 B 🔴 +427 B
assets/MediaTitle.vue_vue_type_script_setup_true_lang-BwJnNy8z.js (removed) 897 B 🟢 -897 B 🟢 -504 B 🟢 -438 B
assets/UserAvatar.vue_vue_type_script_setup_true_lang-CDkUGVOA.js 1.34 kB 1.34 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetButton-CbZj9xHx.js 2.04 kB 2.04 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 7 added / 7 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-C2Yrlvcu.js (new) 7.51 kB 🔴 +7.51 kB 🔴 +1.83 kB 🔴 +1.58 kB
assets/keybindingService-D1xTMDol.js (removed) 7.51 kB 🟢 -7.51 kB 🟢 -1.83 kB 🟢 -1.58 kB
assets/audioService-DkaDXqci.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -959 B 🟢 -822 B
assets/audioService-S3CarKa7.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +960 B 🔴 +823 B
assets/serverConfigStore-uprby_D3.js 2.83 kB 2.83 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 2 added / 2 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-BGg0TRQe.js (new) 1.41 kB 🔴 +1.41 kB 🔴 +650 B 🔴 +546 B
assets/audioUtils-Dfh-DMkm.js (removed) 1.41 kB 🟢 -1.41 kB 🟢 -650 B 🟢 -551 B
assets/mathUtil-CTARWQ-l.js 1.07 kB 1.07 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeFilterUtil-CXKCRJ-m.js 460 B 460 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 1 added / 1 removed

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

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-chart-BhMrpT7H.js 452 kB 452 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-other-Djvsb6Yg.js 3.98 MB 3.98 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-primevue-vP_q3PtU.js 1.96 MB 1.96 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-aR6ntw5X.js 1.37 MB 1.37 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-abEQAgz5.js 232 kB 232 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-abso1wrr.js 160 kB 160 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-BZLod3g9.js 407 kB 407 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Other — 3.82 MB (baseline 3.81 MB) • 🔴 +7.49 kB

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/WidgetRecordAudio-CgcUwJ0z.js (new) 20.4 kB 🔴 +20.4 kB 🔴 +5.23 kB 🔴 +4.62 kB
assets/WidgetRecordAudio-DsycMAlg.js (removed) 20.4 kB 🟢 -20.4 kB 🟢 -5.23 kB 🟢 -4.63 kB
assets/AudioPreviewPlayer-8aI6qwJS.js (new) 13.5 kB 🔴 +13.5 kB 🔴 +3.4 kB 🔴 +3.04 kB
assets/AudioPreviewPlayer-C2C8m1Mp.js (removed) 13.5 kB 🟢 -13.5 kB 🟢 -3.4 kB 🟢 -3.03 kB
assets/NumberControlPopover-DRhvF86X.js (new) 7.49 kB 🔴 +7.49 kB 🔴 +2.16 kB 🔴 +1.9 kB
assets/WidgetGalleria-DoVAPpEj.js (new) 4.1 kB 🔴 +4.1 kB 🔴 +1.44 kB 🔴 +1.3 kB
assets/WidgetGalleria-WwnTvGA1.js (removed) 4.1 kB 🟢 -4.1 kB 🟢 -1.44 kB 🟢 -1.3 kB
assets/WidgetColorPicker-BgkOmjik.js (new) 3.41 kB 🔴 +3.41 kB 🔴 +1.37 kB 🔴 +1.23 kB
assets/WidgetColorPicker-RXBpzp5S.js (removed) 3.41 kB 🟢 -3.41 kB 🟢 -1.38 kB 🟢 -1.23 kB
assets/WidgetMarkdown-2aLm-bPb.js (new) 3.08 kB 🔴 +3.08 kB 🔴 +1.28 kB 🔴 +1.12 kB
assets/WidgetMarkdown-CccUrc7G.js (removed) 3.08 kB 🟢 -3.08 kB 🟢 -1.28 kB 🟢 -1.12 kB
assets/WidgetTextarea-B2P9-pWP.js (new) 2.93 kB 🔴 +2.93 kB 🔴 +1.17 kB 🔴 +1.03 kB
assets/WidgetTextarea-Cld7OFnQ.js (removed) 2.93 kB 🟢 -2.93 kB 🟢 -1.17 kB 🟢 -1.04 kB
assets/WidgetAudioUI-Cv9jT2NZ.js (new) 2.85 kB 🔴 +2.85 kB 🔴 +1.16 kB 🔴 +1.05 kB
assets/WidgetAudioUI-D5X6a1pP.js (removed) 2.85 kB 🟢 -2.85 kB 🟢 -1.16 kB 🟢 -1.05 kB
assets/WidgetInputText-Cu3BUOpn.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +915 B 🔴 +844 B
assets/WidgetInputText-D6O2V7Jv.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -917 B 🟢 -856 B
assets/WidgetToggleSwitch-C8WLR8XC.js (removed) 1.58 kB 🟢 -1.58 kB 🟢 -761 B 🟢 -665 B
assets/WidgetToggleSwitch-DFQ6n4N4.js (new) 1.58 kB 🔴 +1.58 kB 🔴 +761 B 🔴 +668 B
assets/MediaImageBottom-BiB6Ad10.js (removed) 1.57 kB 🟢 -1.57 kB 🟢 -739 B 🟢 -647 B
assets/MediaImageBottom-DkxVVZxR.js (new) 1.57 kB 🔴 +1.57 kB 🔴 +742 B 🔴 +647 B
assets/MediaAudioBottom-9AJHs9_Q.js (removed) 1.52 kB 🟢 -1.52 kB 🟢 -742 B 🟢 -660 B
assets/MediaAudioBottom-Ck_ClSOW.js (new) 1.52 kB 🔴 +1.52 kB 🔴 +743 B 🔴 +656 B
assets/MediaVideoBottom-CHL3hg9J.js (removed) 1.52 kB 🟢 -1.52 kB 🟢 -740 B 🟢 -659 B
assets/MediaVideoBottom-DQ1X_pZg.js (new) 1.52 kB 🔴 +1.52 kB 🔴 +736 B 🔴 +650 B
assets/Media3DBottom-cleR44bm.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +730 B 🔴 +649 B
assets/Media3DBottom-Rpjfi99A.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -732 B 🟢 -653 B
assets/Media3DTop-7Zf-5yuW.js (removed) 1.49 kB 🟢 -1.49 kB 🟢 -761 B 🟢 -650 B
assets/Media3DTop-BQBQk4cT.js (new) 1.49 kB 🔴 +1.49 kB 🔴 +762 B 🔴 +649 B
assets/WidgetSelect-cc84dH7r.js (new) 655 B 🔴 +655 B 🔴 +342 B 🔴 +290 B
assets/WidgetSelect-De5sykhK.js (removed) 655 B 🟢 -655 B 🟢 -340 B 🟢 -291 B
assets/WidgetInputNumber-BjBZIsKK.js (removed) 595 B 🟢 -595 B 🟢 -329 B 🟢 -275 B
assets/WidgetInputNumber-enuS8rf7.js (new) 595 B 🔴 +595 B 🔴 +327 B 🔴 +276 B
assets/Load3D-BdYqS2n2.js (new) 424 B 🔴 +424 B 🔴 +268 B 🔴 +225 B
assets/Load3D-DJWpVl0e.js (removed) 424 B 🟢 -424 B 🟢 -266 B 🟢 -223 B
assets/WidgetLegacy-CfsDs5sT.js (new) 364 B 🔴 +364 B 🔴 +235 B 🔴 +195 B
assets/WidgetLegacy-CY38m7EA.js (removed) 364 B 🟢 -364 B 🟢 -237 B 🟢 -194 B
assets/commands-_s-RvhJR.js 13.6 kB 13.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BuUILW6P.js 13 kB 13 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BV4R6fLx.js 14.9 kB 14.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BWp4HdfU.js 101 B 101 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CLwPdnT6.js 14.2 kB 14.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CWMchBmd.js 15.9 kB 15.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DazTQhtc.js 12.9 kB 12.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DmWrOe93.js 13.7 kB 13.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DwiH7Kr6.js 13.8 kB 13.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-mS3LCNPn.js 14.5 kB 14.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B1JflQcI.js 72.2 kB 72.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B2lyXe48.js 114 kB 114 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B9XEQ-pc.js 94 kB 94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BErKFzc-.js 73.1 kB 73.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bf7Tze-u.js 83.4 kB 83.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BhGMcO4Q.js 84.3 kB 84.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CPZUloNQ.js 99 kB 99 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Cw9RZWRY.js 89 B 89 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Dva0z-T2.js 86.5 kB 86.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-un0K9wDS.js 81.8 kB 81.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-DCIZ2mdC.js 1.46 kB 1.46 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-Du4uz2Wm.js 1.75 kB 1.75 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-59yKN05c.js 2.76 kB 2.76 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-8e6QYQW0.js 283 kB 283 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-A_9dx4yn.js 304 kB 304 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BbD3HDi7.js 307 kB 307 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BOJhIPft.js 369 kB 369 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Bw_Jitw_.js 101 B 101 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C-Pw33mW.js 317 kB 317 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-ChLyG0UJ.js 285 kB 285 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CUVPxA4l.js 342 kB 342 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Dx5Y4xrW.js 310 kB 310 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-JqO5mNmW.js 306 kB 306 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-Beblk1io.js 2.48 kB 2.48 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-CMMfeF2r.js 2.21 kB 2.21 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-BIbGSUAt.js 1.28 kB 1.28 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 19 added / 18 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:L This PR changes 100-499 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Dec 3, 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 Dec 5, 2025
@AustinMroz AustinMroz added New Browser Test Expectations New browser test screenshot should be set by github action size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Dec 5, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad70768 and 0b5ded8.

⛔ Files ignored due to path filters (11)
  • 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/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
📒 Files selected for processing (3)
  • src/locales/en/main.json (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue (0 hunks)
  • src/scripts/app.ts (3 hunks)
💤 Files with no reviewable changes (1)
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{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/scripts/app.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Use es-toolkit for utility functions

Files:

  • src/scripts/app.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/scripts/app.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/scripts/app.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/scripts/app.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/scripts/app.ts
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/scripts/app.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/scripts/app.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/scripts/app.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/scripts/app.ts
src/**/*.{vue,ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

  • src/scripts/app.ts
🧬 Code graph analysis (1)
src/scripts/app.ts (1)
src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts (1)
  • executeNumberControls (57-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: deploy-and-comment
  • GitHub Check: setup
  • GitHub Check: setup
  • GitHub Check: lint-and-format
  • GitHub Check: collect
  • GitHub Check: test
🔇 Additional comments (3)
src/scripts/app.ts (3)

34-34: Import placement and dependency layering look good

The new executeNumberControls import follows the existing pattern of pulling renderer/vueNodes utilities into app.ts (we already import ensureCorrectLayoutScale from the same area), so this dependency direction is consistent and there’s no unused import issue here.


1345-1358: Confirm intended scope and timing of executeNumberControls('before')

executeNumberControls('before') now runs once per batch iteration, after all beforeQueued widget callbacks (main graph + subgraphs) and immediately before graphToPrompt. That means:

  • Controls see the post‑widget state of the graph and affect the prompt that is just about to be queued.
  • The hook runs regardless of whether any number controls are actually registered, assuming the registry is a safe no‑op in that case.
  • For partial executions (queueNodeIds), the hook still runs globally and doesn’t filter by the nodes being executed.

Please double‑check that this ordering and global scope match the intended design for number controls (especially for control‑before‑generate vs control‑after‑generate modes and partial execution flows). If you want controls to be able to key off which nodes will run, they may need access to p or queueNodeIds here.


1396-1408: Clarify executeNumberControls('after') behavior on errors and partial failures

executeNumberControls('after') is invoked only when api.queuePrompt resolves successfully; it is skipped when an exception is thrown because the break inside the catch exits the loop before reaching this call. It still runs when res.node_errors is non‑empty.

This seems reasonable (don’t run “after” controls when the prompt wasn’t queued at all), but it does mean:

  • Any “after‑generate” behavior will not run on PromptExecutionError even if prior hooks (e.g. before) have already adjusted state.
  • “After” hooks will still run when the backend reports node‑level errors in res.node_errors.

Please confirm that this matches the intended UX for control‑after‑generate, especially around failed/partial executions; if you expect symmetrical cleanup or state updates on certain error paths, we may want to call executeNumberControls('after') in a narrower finally around queuePrompt or gate it explicitly on res.prompt_id / lastNodeErrors.

@github-actions github-actions bot removed the New Browser Test Expectations New browser test screenshot should be set by github action label Dec 5, 2025
@DrJKL
Copy link
Contributor

DrJKL commented Dec 5, 2025

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

✅ Actions performed

Full review triggered.

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: 6

Caution

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

⚠️ Outside diff range comments (1)
browser_tests/fixtures/VueNodeHelpers.ts (1)

159-164: Selector now matches current DOM order but is still structurally brittle

Switching decrementButton to nth(1) makes sense if the DOM order is [increment, decrement, control...] and the last button is no longer the decrement control.

Long‑term, this helper will be more stable if the underlying widget exposes semantic hooks (e.g. data-testid="decrement", or accessible labels like “Decrease value”) and the selector keys off those instead of raw button order:

decrementButton: widget.getByRole('button', { name: /decrease/i })

or a data‑attribute locator. Not blocking, but worth considering to align with the “prefer specific selectors in browser tests” guidance.

♻️ Duplicate comments (6)
src/scripts/utils.ts (1)

54-67: These TypeScript violations remain unresolved from the previous review.

The code still contains the same two guideline violations that were flagged previously:

  1. Line 63: (window as any).downloadBlob = fn uses a forbidden as any type assertion
  2. Line 66: .catch(() => {}) silently swallows errors

Based on learnings and coding guidelines, these must be fixed.

The previously suggested fix is still applicable:

+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)
+    })
 }
src/locales/en/main.json (1)

1914-1929: Clarify header key naming and capitalization for number control copy

The new widgets.numberControl strings work functionally, but the header keys/copy are still a bit confusing:

  • controlHeaderBefore vs controlHeaderBefore2 rely on a numeric suffix to distinguish fragments.
  • controlHeaderBefore is sentence‑case (“Automatically update the value”), while controlHeaderAfter/controlHeaderBefore2 are ALL CAPS (“AFTER”, “BEFORE”).

For readability and future maintenance, consider in a follow‑up:

  • Renaming these keys to reflect their role (e.g. headerPrefix, headerModeAfter, headerModeBefore, headerSuffix).
  • Standardizing capitalization (e.g. ALL CAPS for mode labels only, or consistent sentence case) and then adjusting the consuming component to join them into a single clear sentence.

Since this was already raised in an earlier review on this PR, treating it as a non‑blocking polish item is reasonable.

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

119-121: Conditionally render slot wrapper to avoid invisible hitbox.

The absolutely positioned wrapper div renders unconditionally, creating an invisible hitbox when no slot content is provided. This should be guarded with v-if="slots.default" to stay consistent with the inputClass padding logic.

Apply this diff:

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

This was previously flagged in past reviews but appears to still be present.

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

25-37: 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
 }

69-72: 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
src/composables/graph/useGraphNodeManager.ts (1)

102-111: Critical: Assignment operator used instead of comparison in find callback.

Line 104 uses = (assignment) instead of === (comparison). This will:

  1. Mutate every linked widget's name property to 'control_after_generate'
  2. Always return truthy, so it finds the first linked widget regardless of actual name
  3. Cause incorrect behavior and data corruption

Apply this diff to fix:

 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))
   }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

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

⛔ 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 (27)
  • browser_tests/fixtures/VueNodeHelpers.ts (1 hunks)
  • browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts (1 hunks)
  • src/composables/graph/useGraphNodeManager.ts (4 hunks)
  • src/locales/en/main.json (1 hunks)
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue (4 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue (3 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue (2 hunks)
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue (0 hunks)
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts (2 hunks)
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.ts (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts (3 hunks)
  • src/renderer/extensions/vueNodes/widgets/composables/useIntWidget.ts (2 hunks)
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (1 hunks)
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts (1 hunks)
  • src/scripts/app.ts (3 hunks)
  • src/scripts/utils.ts (1 hunks)
  • src/stores/globalSeedStore.ts (1 hunks)
  • src/types/simplifiedWidget.ts (2 hunks)
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts (1 hunks)
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts (2 hunks)
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts (1 hunks)
  • tests-ui/tests/stores/globalSeedStore.test.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/renderer/extensions/vueNodes/widgets/components/layout/WidgetLayoutField.vue
🧰 Additional context used
📓 Path-based instructions (24)
**/*.{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/useIntWidget.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/scripts/app.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue
  • src/scripts/utils.ts
  • tests-ui/tests/stores/globalSeedStore.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/stores/globalSeedStore.ts
  • src/composables/graph/useGraphNodeManager.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • browser_tests/fixtures/VueNodeHelpers.ts
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Use es-toolkit for utility functions

Files:

  • src/renderer/extensions/vueNodes/widgets/composables/useIntWidget.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/scripts/app.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/types/simplifiedWidget.ts
  • src/scripts/utils.ts
  • tests-ui/tests/stores/globalSeedStore.test.ts
  • src/stores/globalSeedStore.ts
  • src/composables/graph/useGraphNodeManager.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • browser_tests/fixtures/VueNodeHelpers.ts
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.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/useIntWidget.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/scripts/app.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/types/simplifiedWidget.ts
  • src/scripts/utils.ts
  • tests-ui/tests/stores/globalSeedStore.test.ts
  • src/stores/globalSeedStore.ts
  • src/composables/graph/useGraphNodeManager.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • browser_tests/fixtures/VueNodeHelpers.ts
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.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/useIntWidget.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/scripts/app.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue
  • src/scripts/utils.ts
  • tests-ui/tests/stores/globalSeedStore.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/stores/globalSeedStore.ts
  • src/composables/graph/useGraphNodeManager.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • browser_tests/fixtures/VueNodeHelpers.ts
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.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/useIntWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/scripts/app.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue
  • src/scripts/utils.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/stores/globalSeedStore.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.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/useIntWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/scripts/app.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/types/simplifiedWidget.ts
  • src/scripts/utils.ts
  • src/stores/globalSeedStore.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.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/useIntWidget.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/scripts/app.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue
  • src/scripts/utils.ts
  • tests-ui/tests/stores/globalSeedStore.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/stores/globalSeedStore.ts
  • src/composables/graph/useGraphNodeManager.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • browser_tests/fixtures/VueNodeHelpers.ts
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.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/useIntWidget.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/scripts/app.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue
  • src/scripts/utils.ts
  • tests-ui/tests/stores/globalSeedStore.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/stores/globalSeedStore.ts
  • src/composables/graph/useGraphNodeManager.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • browser_tests/fixtures/VueNodeHelpers.ts
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.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/useIntWidget.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/scripts/app.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/types/simplifiedWidget.ts
  • src/scripts/utils.ts
  • tests-ui/tests/stores/globalSeedStore.test.ts
  • src/stores/globalSeedStore.ts
  • src/composables/graph/useGraphNodeManager.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • browser_tests/fixtures/VueNodeHelpers.ts
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.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/useIntWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.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/useIntWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/scripts/app.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue
  • src/scripts/utils.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/stores/globalSeedStore.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.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/useIntWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.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/useIntWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/scripts/app.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/types/simplifiedWidget.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue
  • src/scripts/utils.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/stores/globalSeedStore.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.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/useIntWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/composables/graph/useGraphNodeManager.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.ts
**/composables/use*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name composables in the format useXyz.ts

Files:

  • src/renderer/extensions/vueNodes/widgets/composables/useIntWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useControlButtonIcon.ts
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/services/NumberControlRegistry.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/stores/globalSeedStore.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
**/*.{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:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts
  • browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/stores/globalSeedStore.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
browser_tests/**/*.{e2e,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (browser_tests/CLAUDE.md)

browser_tests/**/*.{e2e,spec}.{ts,tsx,js,jsx}: Test user workflows in browser tests
Use Playwright fixtures for browser tests
Follow naming conventions for browser tests
Check assets/ directory for test data when writing tests
Prefer specific selectors in browser tests
Test across multiple viewports

Files:

  • browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.ts
browser_tests/**/*.{test,spec}.ts

📄 CodeRabbit inference engine (AGENTS.md)

Playwright E2E tests can use optional tags like @mobile and @2x which are respected by the Playwright configuration

Files:

  • browser_tests/tests/vueNodes/widgets/int/integerWidget.spec.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/WidgetInputNumber.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.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/WidgetInputNumber.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.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/WidgetInputNumber.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
src/**/stores/**/*.{ts,tsx}

📄 CodeRabbit inference engine (src/CLAUDE.md)

src/**/stores/**/*.{ts,tsx}: Maintain clear public interfaces and restrict extension access in stores
Use TypeScript for type safety in state management stores

Files:

  • src/stores/globalSeedStore.ts
**/stores/*Store.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name Pinia stores with the *Store.ts suffix

Files:

  • src/stores/globalSeedStore.ts
🧠 Learnings (35)
📚 Learning: 2025-11-24T19:47:56.371Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/lib/litegraph/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:56.371Z
Learning: Applies to src/lib/litegraph/**/*.{test,spec}.{ts,tsx} : Use provided test helpers `createTestSubgraph` and `createTestSubgraphNode` from `./fixtures/subgraphHelpers` for consistent subgraph test setup

Applied to files:

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

Applied to files:

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

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/stores/globalSeedStore.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} : Mock external dependencies in tests

Applied to files:

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

Applied to files:

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

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/stores/globalSeedStore.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
📚 Learning: 2025-11-24T19:48:23.088Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:48:23.088Z
Learning: Use Vitest (with happy-dom) for unit and component tests, and Playwright for E2E tests

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/stores/globalSeedStore.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:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • tests-ui/tests/stores/globalSeedStore.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/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue
  • tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.ts
  • src/renderer/extensions/vueNodes/widgets/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 InputSwitch component with ToggleSwitch

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • browser_tests/fixtures/VueNodeHelpers.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
📚 Learning: 2025-11-24T19:47:14.779Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:14.779Z
Learning: Applies to **/*.{ts,tsx} : Never use `as any` type assertions - fix the underlying type issue

Applied to files:

  • src/scripts/utils.ts
📚 Learning: 2025-11-24T19:47:14.779Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:14.779Z
Learning: Applies to **/*.{ts,tsx} : Never use `any` type - use proper TypeScript types

Applied to files:

  • src/scripts/utils.ts
📚 Learning: 2025-11-24T19:47:34.324Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:34.324Z
Learning: Applies to src/**/*.{ts,tsx,vue} : Avoid using ts-expect-error; use proper TypeScript types instead

Applied to files:

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

Applied to files:

  • src/scripts/utils.ts
📚 Learning: 2025-11-24T19:48:23.088Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:48:23.088Z
Learning: Applies to **/stores/*Store.ts : Name Pinia stores with the `*Store.ts` suffix

Applied to files:

  • tests-ui/tests/stores/globalSeedStore.test.ts
  • src/stores/globalSeedStore.ts
📚 Learning: 2025-11-24T19:47:34.324Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:34.324Z
Learning: Applies to src/**/stores/**/*.{ts,tsx} : Maintain clear public interfaces and restrict extension access in stores

Applied to files:

  • tests-ui/tests/stores/globalSeedStore.test.ts
  • src/stores/globalSeedStore.ts
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue OverlayPanel component with Popover

Applied to files:

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

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
  • browser_tests/fixtures/VueNodeHelpers.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/NumberControlPopover.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
📚 Learning: 2025-11-24T19:47:14.779Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:14.779Z
Learning: Applies to **/*.{ts,tsx,vue} : Use `const settingStore = useSettingStore()` and `settingStore.get('Comfy.SomeSetting')` to retrieve settings in TypeScript/Vue files

Applied to files:

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

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/composables/useNumberStepCalculation.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 : Use setup() function in Vue 3 Composition API

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
🧬 Code graph analysis (9)
src/renderer/extensions/vueNodes/widgets/composables/useIntWidget.ts (3)
src/scripts/widgets.ts (1)
  • addValueControlWidget (79-102)
src/lib/litegraph/src/LGraphNode.ts (1)
  • widget (3955-3958)
src/schemas/nodeDef/migration.ts (1)
  • transformInputSpecV2ToV1 (139-143)
tests-ui/tests/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.test.ts (1)
src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts (1)
  • NumberControlRegistry (7-49)
src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts (2)
src/scripts/widgets.ts (1)
  • addValueControlWidgets (104-286)
src/schemas/nodeDef/migration.ts (1)
  • transformInputSpecV2ToV1 (139-143)
src/renderer/extensions/vueNodes/widgets/composables/useFloatWidget.ts (2)
src/scripts/widgets.ts (1)
  • addValueControlWidget (79-102)
src/schemas/nodeDef/migration.ts (1)
  • transformInputSpecV2ToV1 (139-143)
src/scripts/app.ts (1)
src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts (1)
  • executeNumberControls (57-59)
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (4)
src/types/simplifiedWidget.ts (1)
  • ControlWidgetOptions (18-22)
src/stores/globalSeedStore.ts (1)
  • useGlobalSeedStore (4-16)
src/renderer/core/layout/store/layoutStore.ts (1)
  • onChange (926-929)
src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts (1)
  • numberControlRegistry (52-52)
tests-ui/tests/stores/globalSeedStore.test.ts (1)
src/stores/globalSeedStore.ts (1)
  • useGlobalSeedStore (4-16)
src/composables/graph/useGraphNodeManager.ts (2)
src/types/simplifiedWidget.ts (2)
  • SafeControlWidget (23-26)
  • ControlWidgetOptions (18-22)
src/scripts/domWidget.ts (3)
  • value (138-140)
  • value (142-145)
  • isDOMWidget (107-109)
tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useStepperControl.test.ts (2)
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (1)
  • useStepperControl (39-98)
src/renderer/core/layout/store/layoutStore.ts (1)
  • onChange (926-929)

Comment on lines 28 to 35
const { controlMode } = useStepperControl(
modelValue,
{
...props.widget.options,
onChange: handleControlChange
},
props.widget.controlWidget!.value
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unsafe non-null assertions on controlWidget.

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

Consider adding a runtime guard or updating the prop type to guarantee controlWidget is defined:

 const props = defineProps<{
-  widget: SimplifiedWidget<number>
+  widget: SimplifiedWidget<number> & { controlWidget: SafeControlWidget }
   readonly?: boolean
 }>()

Or add a defensive check:

if (!props.widget.controlWidget) {
  throw new Error('WidgetInputNumberWithControl requires a widget with controlWidget')
}
🤖 Prompt for AI Agents
In
src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
around lines 28 to 35 (and usages at line 41), non-null assertions are used on
props.widget.controlWidget which can throw at runtime if undefined; add a
runtime guard at the top of the setup (or component entry) that checks for
props.widget.controlWidget and throws a clear error (or return early) if absent,
or else tighten the prop type to require controlWidget so TypeScript guarantees
presence; ensure all subsequent uses remove the `!` after you’ve validated or
updated the prop type.

Comment on lines +20 to 26
import { isComboInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
import type { BaseDOMWidget } from '@/scripts/domWidget'
import { addValueControlWidgets } from '@/scripts/widgets'
import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
import { addValueControlWidgets } from '@/scripts/widgets'
import { useAssetsStore } from '@/stores/assetsStore'
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Make MultiSelect control wiring explicit and aligned with other combo widgets

Wiring addValueControlWidgets into addMultiSelectWidget is consistent with how combo widgets handle control_after_generate, but here we rely on ComponentWidgetImpl being treated as a combo inside addValueControlWidgets.

To avoid silent misbehavior if the MultiSelect DOM widget is ever not recognized as a combo (e.g., isComboWidget(widget) returning false and falling into the “number” path), consider mirroring the defensive guard used in createInputMappingWidget / addComboWidget:

 const addMultiSelectWidget = (
   node: LGraphNode,
   inputSpec: ComboInputSpec
 ): IBaseWidget => {
@@
   addWidget(node, widget as BaseDOMWidget<object | string>)
   // TODO: Add remote support to multi-select widget
   // https://github.com/Comfy-Org/ComfyUI_frontend/issues/3003
   if (inputSpec.control_after_generate) {
+    if (!isComboWidget(widget)) {
+      throw new Error(`Expected combo widget but received ${widget.type}`)
+    }
     widget.linkedWidgets = addValueControlWidgets(
       node,
       widget,
       undefined,
       undefined,
       transformInputSpecV2ToV1(inputSpec)
     )
   }

Also, please double‑check that isComboWidget(widget) returns true for this MultiSelect wrapper so the control logic follows the combo path rather than the numeric path in addValueControlWidgets.

Also applies to: 72-80

🤖 Prompt for AI Agents
In src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts around
lines 20-26 and 72-80, the MultiSelect widget is wired into
addValueControlWidgets without an explicit guard that it is treated as a combo,
which can cause controls to follow the numeric path if isComboWidget(widget)
becomes false; update the code to mirror the defensive pattern used in
createInputMappingWidget/addComboWidget by checking isComboWidget(widget) (or
otherwise ensuring the ComponentWidgetImpl is flagged/constructed as a combo)
before calling addValueControlWidgets and, if it’s not a combo, fall back to the
intended combo wiring path (or throw/log an explicit error), and add a runtime
assertion or unit-check to confirm isComboWidget returns true for this
MultiSelect wrapper.

Comment on lines +27 to +34
executeControls(phase: 'before' | 'after'): void {
const settingStore = useSettingStore()
if (settingStore.get('Comfy.WidgetControlMode') === phase) {
for (const applyFn of this.controls.values()) {
applyFn()
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling when executing registered controls

Right now, if any applyFn throws, it will stop execution of subsequent controls and bubble the error up to the caller. For a shared registry invoked around generate, that can make a single bad control break the whole flow and conflicts with the guideline to add error handling in services.

Consider catching errors per callback so the rest can still run, and log via your preferred logger (or console.error as a fallback), e.g.:

  executeControls(phase: 'before' | 'after'): void {
    const settingStore = useSettingStore()
    if (settingStore.get('Comfy.WidgetControlMode') === phase) {
-      for (const applyFn of this.controls.values()) {
-        applyFn()
-      }
+      for (const applyFn of this.controls.values()) {
+        try {
+          applyFn()
+        } catch (error) {
+          // Replace with shared logging utility if available
+          console.error('Failed to execute number control', error)
+        }
+      }
    }
  }

As per coding guidelines, services here should include basic error handling.

🤖 Prompt for AI Agents
In src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts
around lines 27 to 34, the executeControls method currently calls each applyFn
directly which lets a thrown error stop the loop; update it to wrap each applyFn
call in a try/catch so a failing control does not prevent subsequent controls
from running, and log the error inside the catch (use the project logger if
available, otherwise console.error) along with identifying context (which phase
and which control key or index) so failures are recorded but execution
continues.

for (const subgraph of this.graph.subgraphs.values()) {
executeWidgetsCallback(subgraph.nodes, 'beforeQueued')
}
executeNumberControls('before')
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling around executeNumberControls to prevent queue processing failures.

If executeNumberControls throws an exception, it will halt the queue processing flow and prevent prompts from being queued. Consider wrapping these calls in try-catch blocks to log errors without breaking the queue.

Apply this diff to add error handling:

         executeWidgetsCallback(this.graph.nodes, 'beforeQueued')
         for (const subgraph of this.graph.subgraphs.values()) {
           executeWidgetsCallback(subgraph.nodes, 'beforeQueued')
         }
-        executeNumberControls('before')
+        try {
+          executeNumberControls('before')
+        } catch (error) {
+          console.error('Error executing number controls (before):', error)
+        }

         const p = await this.graphToPrompt(this.graph)
         executeWidgetsCallback(
           p.workflow.nodes
             .map((n) => this.graph.getNodeById(n.id))
             .filter((n) => !!n),
           'afterQueued'
         )
         for (const subgraph of this.graph.subgraphs.values()) {
           executeWidgetsCallback(subgraph.nodes, 'afterQueued')
         }
-        executeNumberControls('after')
+        try {
+          executeNumberControls('after')
+        } catch (error) {
+          console.error('Error executing number controls (after):', error)
+        }

         this.canvas.draw(true, true)

Also applies to: 1407-1407

🤖 Prompt for AI Agents
In src/scripts/app.ts around lines 1356 and 1407, calls to
executeNumberControls('before') and the similar call at 1407 are unguarded and
can throw, halting queue processing; wrap each call in a try-catch that catches
any exception, logs the error with contextual information (including which
call/location and the error object) using the module's logger (or processLogger)
and then continues without rethrowing so the queue processing isn't interrupted.

Comment on lines 18 to 22
export type ControlWidgetOptions =
| 'fixed'
| 'increment'
| 'decrement'
| '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 | 🟡 Minor

Missing 'linkToGlobal' option in ControlWidgetOptions type.

The ControlWidgetOptions union type does not include 'linkToGlobal', but NumberControlMode.LINK_TO_GLOBAL exists in useStepperControl.ts (line 14). While ENABLE_LINK_TO_GLOBAL is currently false in NumberControlPopover.vue (line 29), the type should be complete to support the full enum when the feature is enabled.

Apply this diff to add the missing option:

 export type ControlWidgetOptions =
   | 'fixed'
   | 'increment'
   | 'decrement'
   | 'randomize'
+  | 'linkToGlobal'
🤖 Prompt for AI Agents
In src/types/simplifiedWidget.ts around lines 18 to 22, the ControlWidgetOptions
union type is missing the 'linkToGlobal' member referenced by
NumberControlMode.LINK_TO_GLOBAL; add 'linkToGlobal' to the union so the type
covers that option (i.e., extend the existing union to include 'linkToGlobal'),
keeping formatting consistent with the other string literals.

Comment on lines +1 to +60
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it } from 'vitest'

import { useGlobalSeedStore } from '@/stores/globalSeedStore'

describe('useGlobalSeedStore', () => {
let store: ReturnType<typeof useGlobalSeedStore>

beforeEach(() => {
setActivePinia(createPinia())
store = useGlobalSeedStore()
})

describe('initialization', () => {
it('should initialize with a random global seed', () => {
expect(typeof store.globalSeed).toBe('number')
expect(store.globalSeed).toBeGreaterThanOrEqual(0)
expect(store.globalSeed).toBeLessThan(1000000)
})

it('should create different seeds for different store instances', () => {
const store1 = useGlobalSeedStore()
setActivePinia(createPinia()) // Reset pinia
const store2 = useGlobalSeedStore()

// Very unlikely to be the same (1 in 1,000,000 chance)
expect(store1.globalSeed).not.toBe(store2.globalSeed)
})
})

describe('setGlobalSeed', () => {
it('should update the global seed value', () => {
const newSeed = 42

store.setGlobalSeed(newSeed)

expect(store.globalSeed).toBe(newSeed)
})

it('should accept any number value', () => {
const testValues = [0, 1, 999999, 1000000, -1, 123.456]

for (const value of testValues) {
store.setGlobalSeed(value)
expect(store.globalSeed).toBe(value)
}
})
})

describe('reactivity', () => {
it('should be reactive when global seed changes', () => {
const initialSeed = store.globalSeed
const newSeed = initialSeed + 100

store.setGlobalSeed(newSeed)

expect(store.globalSeed).toBe(newSeed)
expect(store.globalSeed).not.toBe(initialSeed)
})
})
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Make tests deterministic and align with vitest style (test vs it)

The coverage here is solid, but two improvements would make these tests more robust and consistent with the repo’s unit‑test rules:

  1. Avoid relying on real randomness

'should create different seeds for different store instances' can (very rarely) fail if Math.random() returns the same value twice. You can make this deterministic by mocking Math.random:

-import { beforeEach, describe, expect, it } from 'vitest'
+import { beforeEach, describe, expect, test, vi } from 'vitest'
@@
-  describe('initialization', () => {
-    it('should initialize with a random global seed', () => {
+  describe('initialization', () => {
+    test('should initialize with a random global seed', () => {
@@
-    it('should create different seeds for different store instances', () => {
-      const store1 = useGlobalSeedStore()
-      setActivePinia(createPinia()) // Reset pinia
-      const store2 = useGlobalSeedStore()
-
-      // Very unlikely to be the same (1 in 1,000,000 chance)
-      expect(store1.globalSeed).not.toBe(store2.globalSeed)
-    })
+    test('should create different seeds for different store instances', () => {
+      const randomSpy = vi
+        .spyOn(Math, 'random')
+        .mockReturnValueOnce(0.1)
+        .mockReturnValueOnce(0.9)
+
+      const store1 = useGlobalSeedStore()
+      setActivePinia(createPinia()) // Reset pinia
+      const store2 = useGlobalSeedStore()
+
+      expect(store1.globalSeed).not.toBe(store2.globalSeed)
+
+      randomSpy.mockRestore()
+    })
@@
-  describe('setGlobalSeed', () => {
-    it('should update the global seed value', () => {
+  describe('setGlobalSeed', () => {
+    test('should update the global seed value', () => {
@@
-    it('should accept any number value', () => {
+    test('should accept any number value', () => {
@@
-  describe('reactivity', () => {
-    it('should be reactive when global seed changes', () => {
+  describe('reactivity', () => {
+    test('should be reactive when global seed changes', () => {

This keeps the semantics the same while removing flakiness and matching the test‑preferred style for Vitest in this repo.

}

function validateControlWidgetValue(val: unknown): ControlWidgetOptions {
//TODO: Is there a way to do this without repeating?
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd do something like I did with the expandables.

const CONTROL_OPTIONS = [ 'fixed', 'increment', 'decrement', 'randomize' ] as const
export function validateControlOption(maybeOption: string): ControlOption {
 if (CONTROL_OPTIONS.includes(maybeOption as ControlOption)) return maybeOption
 return 'randomize'
}

You could also use a type guard in there.

Comment on lines 1915 to 1918
"controlHeaderBefore": "Automatically update the value",
"controlHeaderAfter": "AFTER",
"controlHeaderBefore2": "BEFORE",
"controlHeaderEnd": "running the workflow:",
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional: You could nest these

Suggested change
"controlHeaderBefore": "Automatically update the value",
"controlHeaderAfter": "AFTER",
"controlHeaderBefore2": "BEFORE",
"controlHeaderEnd": "running the workflow:",
"controlHeader": {
"Before": "Automatically update the value",
"After": "AFTER",
"Before2": "BEFORE",
"End": "running the workflow:",
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

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

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

Apply this diff:

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

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

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

Apply this diff for consistency:

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

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

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

Consider one of these solutions:

Option 1: Add a runtime guard at component entry:

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

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

// Then remove the `!` assertions below

Option 2: Tighten the prop type to require controlWidget:

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

// Then remove the `!` assertions below

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

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

87-96: Use strict equality operator.

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

Apply this diff:

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

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

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

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

📄 CodeRabbit inference engine (.cursorrules)

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

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

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

Files:

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

📄 CodeRabbit inference engine (.cursorrules)

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

Files:

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

📄 CodeRabbit inference engine (.cursorrules)

Implement proper error handling in components and services

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

Files:

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

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

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

Files:

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

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

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

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

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

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

Files:

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

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

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

📄 CodeRabbit inference engine (src/CLAUDE.md)

Clean up subscriptions in state management to prevent memory leaks

Files:

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

📄 CodeRabbit inference engine (src/CLAUDE.md)

Follow Vue 3 composition API style guide

Files:

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

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

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

📄 CodeRabbit inference engine (.cursorrules)

Use es-toolkit for utility functions

Files:

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

📄 CodeRabbit inference engine (.cursorrules)

Use TypeScript for type safety

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

Files:

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

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

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

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

📄 CodeRabbit inference engine (src/CLAUDE.md)

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

Files:

  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/composables/graph/useGraphNodeManager.ts
**/composables/use*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Name composables in the format useXyz.ts

Files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberWithControl.vue
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Use setup() function in Vue 3 Composition API

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Implement computed() for derived state in Vue 3 Composition API

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • src/composables/graph/useGraphNodeManager.ts
📚 Learning: 2025-11-24T19:47:45.616Z
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue OverlayPanel component with Popover

Applied to files:

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

Applied to files:

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

Applied to files:

  • src/renderer/extensions/vueNodes/widgets/components/NumberControlPopover.vue
🧬 Code graph analysis (1)
src/renderer/extensions/vueNodes/widgets/composables/useStepperControl.ts (3)
src/types/simplifiedWidget.ts (1)
  • ControlOptions (24-24)
src/stores/globalSeedStore.ts (1)
  • useGlobalSeedStore (4-16)
src/renderer/extensions/vueNodes/widgets/services/NumberControlRegistry.ts (1)
  • numberControlRegistry (52-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: deploy-and-comment
  • GitHub Check: collect
  • GitHub Check: lint-and-format
  • GitHub Check: test
  • GitHub Check: setup
🔇 Additional comments (12)
src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue (2)

3-3: LGTM!

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


72-72: LGTM!

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

Also applies to: 94-98

src/types/simplifiedWidget.ts (2)

18-30: LGTM!

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


32-35: LGTM!

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

Also applies to: 68-68

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

23-24: LGTM!

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


130-131: LGTM!

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

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

9-23: LGTM!

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


25-37: LGTM!

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


39-54: LGTM!

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

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

1-87: LGTM!

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

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


89-171: LGTM!

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

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

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

45-67: LGTM!

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

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

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Verify intentional ALL CAPS usage for mode labels.

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

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

Example alternative structure:

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

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

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

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

Choose a reason for hiding this comment

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

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

*/
export function useNumberStepCalculation(
options: NumberWidgetOptions | undefined,
precision: Ref<number | undefined>,
Copy link
Contributor

Choose a reason for hiding this comment

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

https://vuejs.org/api/reactivity-utilities.html#tovalue

This is a good place for MaybeRefOrGetter

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

Choose a reason for hiding this comment

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

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

That thing we talked about

Comment on lines +12 to +18
type ControlOption = {
description: string
icon?: string
mode: NumberControlMode
text?: string
title: string
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Alphabetical is great, but required before optional please <3


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

Choose a reason for hiding this comment

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


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

Choose a reason for hiding this comment

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

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

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

'randomize'
] as const
export type ControlOptions = (typeof CONTROL_OPTIONS)[number]

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
function isValidControlOption(optionMaybe: string): optionMaybe is ControlOption {
return CONTROL_OPTIONS.includes(optionMaybe as ControlOptions)
}

] as const
export type ControlOptions = (typeof CONTROL_OPTIONS)[number]

export function validateControlOption(val: unknown): ControlOptions {
Copy link
Contributor

Choose a reason for hiding this comment

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

Also, this is technically normalizing, right?


describe('useStepperControl', () => {
beforeEach(() => {
setActivePinia(createPinia())
Copy link
Contributor

Choose a reason for hiding this comment

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

createTestingPinia please

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this piece could be pruned for now, it's a deserving feature and should be treated with such deference.

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:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants