Skip to content

Conversation

@DrJKL
Copy link
Contributor

@DrJKL DrJKL commented Nov 20, 2025

Summary

Replicate the alt+drag to clone behavior present in litegraph.

Changes

  • What: Simplify the interaction/drag handling, now with less state!
  • What: Alt+Click+Drag a node to clone it

Screenshots (if applicable)

Screen.Recording.2025-11-20.215637.mp4

┆Issue is synchronized with this Notion page by Unito

@github-actions
Copy link

github-actions bot commented Nov 20, 2025

🎨 Storybook Build Status

Build completed successfully!

⏰ Completed at: 11/21/2025, 09:33:18 PM UTC

🔗 Links


🎉 Your Storybook is ready for review!

@github-actions
Copy link

github-actions bot commented Nov 20, 2025

🎭 Playwright Test Results

Some tests failed

⏰ Completed at: 11/21/2025, 09:43:22 PM UTC

📈 Summary

  • Total Tests: 495
  • Passed: 482 ✅
  • Failed: 2 ❌
  • Flaky: 2 ⚠️
  • Skipped: 9 ⏭️

📊 Test Reports by Browser

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

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

@github-actions
Copy link

github-actions bot commented Nov 20, 2025

Bundle Size Report

Summary

  • Raw size: 13.7 MB baseline 13.7 MB — 🟢 -4.14 kB
  • Gzip: 2.76 MB baseline 2.76 MB — 🟢 -622 B
  • Brotli: 2.16 MB baseline 2.16 MB — 🟢 -320 B
  • Bundles: 92 current • 92 baseline • 38 added / 38 removed

Category Glance
Graph Workspace 🟢 -4.35 kB (940 kB) · App Entry Points 🔴 +209 B (3.13 MB) · Vendor & Third-Party ⚪ 0 B (5.32 MB) · Other ⚪ 0 B (3.87 MB) · Panels & Settings ⚪ 0 B (306 kB) · UI Components ⚪ 0 B (141 kB) · + 3 more

Per-category breakdown
App Entry Points — 3.13 MB (baseline 3.13 MB) • 🔴 +209 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-DVQIMXjE.js (new) 2.77 MB 🔴 +2.77 MB 🔴 +579 kB 🔴 +440 kB
assets/index-BqeDE35L.js (removed) 2.77 MB 🟢 -2.77 MB 🟢 -579 kB 🟢 -440 kB
assets/index-B3xJWg7Y.js (new) 364 kB 🔴 +364 kB 🔴 +75.2 kB 🔴 +61.3 kB
assets/index-CSmWEL8B.js (removed) 364 kB 🟢 -364 kB 🟢 -75.2 kB 🟢 -61.3 kB
assets/index-CMAOIduZ.js (new) 345 B 🔴 +345 B 🔴 +245 B 🔴 +198 B
assets/index-CvzUm_tn.js (removed) 345 B 🟢 -345 B 🟢 -246 B 🟢 -234 B

Status: 3 added / 3 removed

Graph Workspace — 940 kB (baseline 945 kB) • 🟢 -4.35 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-DLNBe3qp.js (removed) 945 kB 🟢 -945 kB 🟢 -183 kB 🟢 -140 kB
assets/GraphView-Due49AX1.js (new) 940 kB 🔴 +940 kB 🔴 +182 kB 🔴 +140 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/UserSelectView-CS8nqszk.js (removed) 7.97 kB 🟢 -7.97 kB 🟢 -2.43 kB 🟢 -2.14 kB
assets/UserSelectView-DnJRVajZ.js (new) 7.97 kB 🔴 +7.97 kB 🔴 +2.44 kB 🔴 +2.14 kB

Status: 1 added / 1 removed

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CreditsPanel-CIdfp4m7.js (removed) 22.9 kB 🟢 -22.9 kB 🟢 -5.46 kB 🟢 -4.78 kB
assets/CreditsPanel-DTIa67vP.js (new) 22.9 kB 🔴 +22.9 kB 🔴 +5.46 kB 🔴 +4.78 kB
assets/KeybindingPanel-DBqUgDvi.js (new) 15.1 kB 🔴 +15.1 kB 🔴 +3.73 kB 🔴 +3.3 kB
assets/KeybindingPanel-GDvtV8yk.js (removed) 15.1 kB 🟢 -15.1 kB 🟢 -3.73 kB 🟢 -3.29 kB
assets/ExtensionPanel-Cr3jHNau.js (removed) 11.9 kB 🟢 -11.9 kB 🟢 -2.79 kB 🟢 -2.44 kB
assets/ExtensionPanel-DaorejXP.js (new) 11.9 kB 🔴 +11.9 kB 🔴 +2.79 kB 🔴 +2.45 kB
assets/AboutPanel-CC4KIvts.js (new) 10.1 kB 🔴 +10.1 kB 🔴 +2.62 kB 🔴 +2.31 kB
assets/AboutPanel-ilFLqDBM.js (removed) 10.1 kB 🟢 -10.1 kB 🟢 -2.62 kB 🟢 -2.31 kB
assets/ServerConfigPanel-B_rooArx.js (removed) 8.02 kB 🟢 -8.02 kB 🟢 -2.12 kB 🟢 -1.87 kB
assets/ServerConfigPanel-OIZ9WhSf.js (new) 8.02 kB 🔴 +8.02 kB 🔴 +2.12 kB 🔴 +1.87 kB
assets/UserPanel-CQCbRtak.js (removed) 7.74 kB 🟢 -7.74 kB 🟢 -2.03 kB 🟢 -1.77 kB
assets/UserPanel-CyWKoKfx.js (new) 7.74 kB 🔴 +7.74 kB 🔴 +2.03 kB 🔴 +1.77 kB
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 — 141 kB (baseline 141 kB) • ⚪ 0 B

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/Load3D.vue_vue_type_script_setup_true_lang-DvuylHt_.js (new) 53.9 kB 🔴 +53.9 kB 🔴 +8.43 kB 🔴 +7.24 kB
assets/Load3D.vue_vue_type_script_setup_true_lang-iz7y7LTN.js (removed) 53.9 kB 🟢 -53.9 kB 🟢 -8.43 kB 🟢 -7.25 kB
assets/WidgetSelect.vue_vue_type_script_setup_true_lang-BAsnft-2.js (removed) 48.1 kB 🟢 -48.1 kB 🟢 -10.3 kB 🟢 -8.93 kB
assets/WidgetSelect.vue_vue_type_script_setup_true_lang-DYlMVlxF.js (new) 48.1 kB 🔴 +48.1 kB 🔴 +10.3 kB 🔴 +8.93 kB
assets/WidgetInputNumber.vue_vue_type_script_setup_true_lang-CE6DoBtR.js (removed) 12.7 kB 🟢 -12.7 kB 🟢 -3.3 kB 🟢 -2.92 kB
assets/WidgetInputNumber.vue_vue_type_script_setup_true_lang-SfYRB_JM.js (new) 12.7 kB 🔴 +12.7 kB 🔴 +3.31 kB 🔴 +2.92 kB
assets/ComfyQueueButton-CgLCilzK.js (removed) 9.22 kB 🟢 -9.22 kB 🟢 -2.5 kB 🟢 -2.2 kB
assets/ComfyQueueButton-Dw5Vhf7u.js (new) 9.22 kB 🔴 +9.22 kB 🔴 +2.5 kB 🔴 +2.2 kB
assets/WidgetLayoutField.vue_vue_type_script_setup_true_lang-2f-HA1z5.js (removed) 2.14 kB 🟢 -2.14 kB 🟢 -795 B 🟢 -699 B
assets/WidgetLayoutField.vue_vue_type_script_setup_true_lang-BNUC3wf4.js (new) 2.14 kB 🔴 +2.14 kB 🔴 +794 B 🔴 +689 B
assets/MediaTitle.vue_vue_type_script_setup_true_lang-Ca_iTnC_.js (removed) 848 B 🟢 -848 B 🟢 -473 B 🟢 -414 B
assets/MediaTitle.vue_vue_type_script_setup_true_lang-DF2dZFDz.js (new) 848 B 🔴 +848 B 🔴 +476 B 🔴 +415 B
assets/LazyImage.vue_vue_type_script_setup_true_lang-Wi-CcgaU.js 10.7 kB 10.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserAvatar.vue_vue_type_script_setup_true_lang-D2s8tnS2.js 1.26 kB 1.26 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetButton-ByrPd5jr.js 1.62 kB 1.62 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 6 added / 6 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-B57FdIrD.js (removed) 7.51 kB 🟢 -7.51 kB 🟢 -1.84 kB 🟢 -1.58 kB
assets/keybindingService-bHuIRFg4.js (new) 7.51 kB 🔴 +7.51 kB 🔴 +1.83 kB 🔴 +1.59 kB
assets/audioService-C-197EnQ.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +963 B 🔴 +824 B
assets/audioService-MJGa8ySi.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -962 B 🟢 -822 B
assets/serverConfigStore-BoHtzifw.js 2.79 kB 2.79 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-B8x6L2ed.js (removed) 1.41 kB 🟢 -1.41 kB 🟢 -652 B 🟢 -550 B
assets/audioUtils-Bl36Jcb-.js (new) 1.41 kB 🔴 +1.41 kB 🔴 +652 B 🔴 +546 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 — 5.32 MB (baseline 5.32 MB) • ⚪ 0 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-other-z0ajCJVX.js 3.22 MB 3.22 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-primevue-PESgPnbc.js 517 B 517 B ⚪ 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-D2zb6Fg1.js 232 kB 232 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-aBQ_uOio.js 92.6 kB 92.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-BZLod3g9.js 407 kB 407 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Other — 3.87 MB (baseline 3.87 MB) • ⚪ 0 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/WidgetRecordAudio-CTDkAtPL.js (removed) 21.9 kB 🟢 -21.9 kB 🟢 -5.52 kB 🟢 -4.88 kB
assets/WidgetRecordAudio-oetl1lEN.js (new) 21.9 kB 🔴 +21.9 kB 🔴 +5.52 kB 🔴 +4.87 kB
assets/AudioPreviewPlayer-DjIWU2G3.js (new) 14.9 kB 🔴 +14.9 kB 🔴 +3.69 kB 🔴 +3.3 kB
assets/AudioPreviewPlayer-DQ63LDdH.js (removed) 14.9 kB 🟢 -14.9 kB 🟢 -3.69 kB 🟢 -3.3 kB
assets/WidgetGalleria-BWHVvPHK.js (new) 5.56 kB 🔴 +5.56 kB 🔴 +1.75 kB 🔴 +1.54 kB
assets/WidgetGalleria-CZ6J1V5U.js (removed) 5.56 kB 🟢 -5.56 kB 🟢 -1.74 kB 🟢 -1.54 kB
assets/WidgetColorPicker-Cd8CFzWJ.js (new) 4.87 kB 🔴 +4.87 kB 🔴 +1.69 kB 🔴 +1.48 kB
assets/WidgetColorPicker-CT9G_pZJ.js (removed) 4.87 kB 🟢 -4.87 kB 🟢 -1.69 kB 🟢 -1.48 kB
assets/WidgetMarkdown-Bg5reevy.js (new) 4.6 kB 🔴 +4.6 kB 🔴 +1.6 kB 🔴 +1.39 kB
assets/WidgetMarkdown-FeFnVT--.js (removed) 4.6 kB 🟢 -4.6 kB 🟢 -1.6 kB 🟢 -1.39 kB
assets/WidgetAudioUI-BU4ZgRBs.js (removed) 4.33 kB 🟢 -4.33 kB 🟢 -1.44 kB 🟢 -1.28 kB
assets/WidgetAudioUI-DtiAXvUS.js (new) 4.33 kB 🔴 +4.33 kB 🔴 +1.44 kB 🔴 +1.29 kB
assets/WidgetTextarea-D5qpRBiM.js (new) 3.72 kB 🔴 +3.72 kB 🔴 +1.31 kB 🔴 +1.15 kB
assets/WidgetTextarea-iE4qzPCO.js (removed) 3.72 kB 🟢 -3.72 kB 🟢 -1.3 kB 🟢 -1.15 kB
assets/WidgetInputText-CMenZyNc.js (new) 3.45 kB 🔴 +3.45 kB 🔴 +1.24 kB 🔴 +1.09 kB
assets/WidgetInputText-Df1W2DzK.js (removed) 3.45 kB 🟢 -3.45 kB 🟢 -1.24 kB 🟢 -1.09 kB
assets/WidgetToggleSwitch-DnSw4pRn.js (new) 3.23 kB 🔴 +3.23 kB 🔴 +1.14 kB 🔴 +1.01 kB
assets/WidgetToggleSwitch-DnxdKKrR.js (removed) 3.23 kB 🟢 -3.23 kB 🟢 -1.14 kB 🟢 -1.01 kB
assets/MediaImageBottom-CpTnMxPj.js (removed) 3.05 kB 🟢 -3.05 kB 🟢 -1.05 kB 🟢 -908 B
assets/MediaImageBottom-DjP-sA6t.js (new) 3.05 kB 🔴 +3.05 kB 🔴 +1.05 kB 🔴 +910 B
assets/MediaAudioBottom-DqGI3lah.js (removed) 3 kB 🟢 -3 kB 🟢 -1.05 kB 🟢 -922 B
assets/MediaAudioBottom-pbZjE3h1.js (new) 3 kB 🔴 +3 kB 🔴 +1.05 kB 🔴 +928 B
assets/Media3DTop-CvzAQ4Mv.js (removed) 3 kB 🟢 -3 kB 🟢 -1.08 kB 🟢 -913 B
assets/Media3DTop-DtVeGP7Y.js (new) 3 kB 🔴 +3 kB 🔴 +1.08 kB 🔴 +911 B
assets/MediaVideoBottom-Dt9Xg1eu.js (removed) 3 kB 🟢 -3 kB 🟢 -1.04 kB 🟢 -925 B
assets/MediaVideoBottom-Dx_MYg-B.js (new) 3 kB 🔴 +3 kB 🔴 +1.05 kB 🔴 +934 B
assets/Media3DBottom-1CrdFTJq.js (removed) 2.98 kB 🟢 -2.98 kB 🟢 -1.04 kB 🟢 -913 B
assets/Media3DBottom-CQ8zC0r2.js (new) 2.98 kB 🔴 +2.98 kB 🔴 +1.04 kB 🔴 +923 B
assets/WidgetSelect-D6wXrYlu.js (new) 2.17 kB 🔴 +2.17 kB 🔴 +676 B 🔴 +571 B
assets/WidgetSelect-Df_pDbcg.js (removed) 2.17 kB 🟢 -2.17 kB 🟢 -672 B 🟢 -576 B
assets/WidgetInputNumber-9XcTEjG4.js (removed) 2.12 kB 🟢 -2.12 kB 🟢 -664 B 🟢 -557 B
assets/WidgetInputNumber-u5fqsR6x.js (new) 2.12 kB 🔴 +2.12 kB 🔴 +666 B 🔴 +556 B
assets/Load3D-C2txNgMx.js (new) 1.94 kB 🔴 +1.94 kB 🔴 +597 B 🔴 +533 B
assets/Load3D-DACEhMik.js (removed) 1.94 kB 🟢 -1.94 kB 🟢 -598 B 🟢 -501 B
assets/WidgetLegacy-Bzxxzryx.js (removed) 1.88 kB 🟢 -1.88 kB 🟢 -561 B 🟢 -470 B
assets/WidgetLegacy-C2_WojQN.js (new) 1.88 kB 🔴 +1.88 kB 🔴 +562 B 🔴 +514 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-CLwPdnT6.js 14.2 kB 14.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CWMchBmd.js 15.9 kB 15.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DazTQhtc.js 12.9 kB 12.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DmWrOe93.js 13.7 kB 13.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DwiH7Kr6.js 13.8 kB 13.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-mS3LCNPn.js 14.5 kB 14.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-5lOBdqcC.js 84.5 kB 84.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BOCuaVpE.js 73.4 kB 73.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-ClrEFGUz.js 72.4 kB 72.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CyNU0iQX.js 99.3 kB 99.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D7gwLxft.js 114 kB 114 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DC8o4BCt.js 86.8 kB 86.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DKiesCV4.js 94.3 kB 94.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Hq2q-OtB.js 83.6 kB 83.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-USAlAlnj.js 82 kB 82 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-RTI8pWy9.js 1.42 kB 1.42 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-Cxl4dc80.js 1.68 kB 1.68 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-BB0lT7C5.js 2.7 kB 2.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-_Px5dSNW.js 306 kB 306 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-7z21KPoS.js 285 kB 285 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BWKZzBPK.js 346 kB 346 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CGbgH4Yl.js 320 kB 320 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CjjjdWkV.js 313 kB 313 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CVrNtxvj.js 288 kB 288 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DLRSA0IK.js 309 kB 309 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DQV2gnwA.js 372 kB 372 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-ofqLG5vz.js 310 kB 310 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-rDmYEWg5.js 2.39 kB 2.39 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-Ds3K3ULR.js 2.15 kB 2.15 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-BIbGSUAt.js 1.28 kB 1.28 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 18 added / 18 removed

@DrJKL DrJKL requested a review from Myestery November 20, 2025 23:38
@DrJKL DrJKL changed the title WIP: Alt+Drag to clone - Vue Nodes Feat: Alt+Drag to clone - Vue Nodes Nov 21, 2025
@DrJKL DrJKL marked this pull request as ready for review November 21, 2025 05:58
@DrJKL DrJKL requested a review from jtydhr88 as a code owner November 21, 2025 05:58
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Nov 21, 2025
@DrJKL DrJKL force-pushed the drjkl/the-prestige branch from ecf499c to 6f17802 Compare November 21, 2025 06:07
Copy link
Contributor

@christian-byrne christian-byrne left a comment

Choose a reason for hiding this comment

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

LGTM although didn't test and also saw a console log commented out

@DrJKL
Copy link
Contributor Author

DrJKL commented Nov 21, 2025

LGTM although didn't test and also saw a console log commented out

I tracked down the log and sent it to the cornfield. Please forgive me 🙇🏻

@DrJKL DrJKL enabled auto-merge (squash) November 21, 2025 06:42
@coderabbitai
Copy link

coderabbitai bot commented Nov 21, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (2)
  • browser_tests/tests/interaction.spec.ts-snapshots/dragged-node1-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/rightClickMenu.spec.ts-snapshots/right-click-unpinned-node-moved-chromium-linux.png is excluded by !**/*.png

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Typed node IDs and node position tuples; moved node drag/snap into a new shared useNodeDrag composable; converted useTransformState into a shared composable and removed the TransformState injection key; refactored event/pointer APIs to use NodeId; added LGraphCanvas.cloneNodes and Alt-drag-to-clone; updated tests.

Changes

Cohort / File(s) Summary
Graph & Typing
src/composables/graph/useGraphNodeManager.ts, src/renderer/core/spatial/boundsCalculator.ts, src/renderer/extensions/vueNodes/interactions/resize/useNodeResize.ts
VueNodeData.id type changed to NodeId. PositionedNode made exported and tightened from ArrayLike<number> to [number, number] tuples. UseNodeResizeOptions.transformState typing switched to ReturnType<typeof useTransformState>.
Transform State Migration
src/renderer/core/layout/injectionKeys.ts, src/renderer/core/layout/transform/useTransformState.ts, src/renderer/core/layout/transform/TransformPane.vue
Removed the TransformState interface and TransformStateKey injection. useTransformState now wrapped with createSharedComposable(...). TransformPane.vue consumes a reduced transform surface (camera, transformStyle, syncWithCanvas) and no longer provides/injects the key.
Node Dragging Consolidation
src/renderer/extensions/vueNodes/layout/useNodeDrag.ts, src/renderer/extensions/vueNodes/layout/useNodeLayout.ts
Introduced shared useNodeDrag (startDrag, handleDrag, endDrag via createSharedComposable) implementing multi-select dragging, RAF throttling, and snap-on-end. useNodeLayout simplified: removed built-in drag/snap handlers and exposes moveNodeTo plus basic position/size/zIndex.
Event & Pointer API Refactor
src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts, src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts, src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts
Event handlers now operate on NodeId (removed VueNodeData), useNodePointerInteractions now accepts a nodeIdRef. Pointer API rewritten to explicit handlers wired to useNodeDrag. Tests updated to use node ID strings and richer mocked layoutStore; several legacy handlers removed from public surface.
LGraph & Clone Behavior
src/lib/litegraph/src/LGraphCanvas.ts, src/renderer/extensions/vueNodes/components/LGraphNode.vue
Added LGraphCanvas.cloneNodes(nodes: Positionable[]) and routed onMenuNodeClone through it. LGraphNode.vue adds Alt-drag-to-clone behavior (calls cloneNodes, starts drag on clone), updates pointer binding, cursor classes, and z-index handling.
Event Propagation & Minimap Typing
src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts, src/renderer/extensions/minimap/data/AbstractMinimapDataSource.ts
Added event.stopPropagation() to pointer move/up handlers. Minimap bounds mapping now annotates elements as the tightened PositionedNode type.
Tests & Fixtures
tests-ui/..., browser_tests/fixtures/ComfyPage.ts
Tests updated to reuse a shared useTransformState instance, tightened tuple typings, switched from provide/inject to mocking useTransformState, removed the heavy transformPerformance test, and adjusted node-related tests to use NodeId strings. Browser fixture drag uses finer-grained mouse.move steps.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant NodeComp as LGraphNode
    participant Canvas as LGraphCanvas
    participant Pointer as useNodePointerInteractions
    participant Drag as useNodeDrag
    participant Layout as LayoutStore

    User->>NodeComp: Alt + PointerDown
    NodeComp->>Canvas: cloneNodes([node])
    Canvas-->>NodeComp: cloned node (offset)
    NodeComp->>Pointer: pointerdown(event, nodeId)
    Pointer->>Drag: startDrag(event, nodeId)
    Drag->>Layout: mark dragging & record starts

    User->>NodeComp: PointerMove
    NodeComp->>Pointer: pointermove(event)
    Pointer->>Drag: handleDrag(event, nodeId)
    Drag->>Layout: RAF-updates positions

    User->>NodeComp: PointerUp
    NodeComp->>Pointer: pointerup(event)
    Pointer->>Drag: endDrag(nodeId)
    Drag->>Layout: apply snap, batch final updates, clear dragging
Loading

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feat: Alt+Drag to clone - Vue Nodes' directly and clearly describes the main feature added—Alt+drag-to-clone functionality for Vue nodes, which is the primary objective of this changeset.
Description check ✅ Passed The PR description provides a summary and lists key changes, but is missing the 'Breaking' and 'Dependencies' sections from the template. However, the core information (summary, what changed) is present, making it mostly complete.

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts (1)

52-64: vi.resetAllMocks will break your manual module mocks

All your module mocks (useCanvasStore, useLayoutMutations, useGraphNodeManager, useCanvasInteractions) are implemented with vi.fn(() => ...) inside vi.mock factories. Calling vi.resetAllMocks() in beforeEach (line 84) resets each mock's implementation, causing useCanvasStore() and friends to return undefined instead of the configured instances once this suite is un-skipped.

To preserve the factory implementations while clearing call history, use vi.clearAllMocks() instead. This keeps the mocks usable across tests while still resetting call counts.

Also applies to: 17-44 (all module mocks), 75 (describe.skip), 83-86 (beforeEach)

🧹 Nitpick comments (11)
src/lib/litegraph/src/LGraphCanvas.ts (1)

1774-1803: Shared cloneNodes helper looks correct; consider minimal hardening for external callers

Centralizing cloning into cloneNodes and having onMenuNodeClone respect selectedItems (falling back to the clicked node) is a solid improvement and keeps behavior consistent with the existing serialize/deserialize flow. The +5/+5 offset via _deserializeItems will yield the expected “nudged” clones for single and multi-selection.

Two small, non-blocking suggestions:

  • Add a defensive guard for LGraphCanvas.active_canvas inside cloneNodes (e.g. early return or a clear error) so that accidental calls when no canvas is active fail more predictably than with a generic TypeError.
  • Optionally treat an empty nodes array as a no-op inside cloneNodes (you already guard in onMenuNodeClone, but this would make the helper safer for other static call sites, such as Vue alt‑drag integration).

These don’t change semantics but would make the new static helper more robust if it’s reused elsewhere.

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

16-69: Align GraphNodeManager API with NodeId for consistency

Updating VueNodeData.id to NodeId is good for type clarity, but vueNodeData and getNode still expose string in their signatures. To keep things consistent and future‑proof if NodeId ever stops being a bare string, consider:

-export interface GraphNodeManager {
-  // Reactive state - safe data extracted from LiteGraph nodes
-  vueNodeData: ReadonlyMap<string, VueNodeData>
-
-  // Access to original LiteGraph nodes (non-reactive)
-  getNode(id: string): LGraphNode | undefined
+export interface GraphNodeManager {
+  // Reactive state - safe data extracted from LiteGraph nodes
+  vueNodeData: ReadonlyMap<NodeId, VueNodeData>
+
+  // Access to original LiteGraph nodes (non-reactive)
+  getNode(id: NodeId): LGraphNode | undefined

and updating the internal Maps/signatures accordingly. This is purely a typing/ergonomics improvement; behavior stays the same.

tests-ui/tests/renderer/extensions/vueNodes/components/LGraphNode.test.ts (1)

17-26: Transform state mock is sufficient but slightly under-specified

The mock only exposes screenToCanvas, canvasToScreen, camera, and isNodeInViewport. That’s fine for current LGraphNode usage, but if the component later destructures transformStyle or syncWithCanvas from useTransformState, tests will start failing due to missing properties. Consider expanding the mock to match the real composable surface to make these tests more future‑proof.

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

252-303: toggleNodeSelectionAfterPointerUp tests appear inconsistent

Two tests call toggleNodeSelectionAfterPointerUp('node-1', true) with effectively the same local setup (mockNode!.selected = true), but they assert opposite behaviors (one expects deselect + update, the other expects no updates) while comments describe different scenarios (“selected at pointer down” vs “not previously selected”).

If the real behavior depends on additional state (e.g. something set during pointer‑down), consider:

  • Explicitly driving that state via handleNodeSelect in each test, or
  • Adjusting the comments and expectations so the scenarios and assertions actually differ.

Right now these tests are skipped, but un‑skipping them in this state will likely cause confusion even if they pass by accident.

src/renderer/extensions/vueNodes/layout/useNodeDrag.ts (1)

18-215: Drag implementation is correct and nicely factored

The shared useNodeDrag composable cleanly handles:

  • Capturing initial node + multi‑selection positions and mouse origin in canvas space.
  • Using screenToCanvas to compute deltas, then applying them via moveNode for the primary and other selected nodes.
  • Throttling updates with requestAnimationFrame and deferring pointer capture to drag time (which should play well with Alt+drag cloning).
  • Applying snap‑to‑grid only on drag end via batchUpdateNodeBounds, with proper change checks and shift‑key–driven behavior.
  • Cleaning up drag state, shift tracking, and any pending RAF in endDrag.

If you ever need concurrent drags (e.g. multi‑pointer scenarios), the shared, single‑instance state will need revisiting, but for standard canvas interactions this design is appropriate.

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

22-23: Global dragging cursor may affect all nodes, not just the active one

cursorClass derives from layoutStore.isDraggingVueNodes.value, so while any Vue node is being dragged, all non‑pinned nodes render with cursor-grabbing. If the intent is to visually indicate a “global dragging” mode, this is fine; if you only want the actively dragged nodes to show the grabbing cursor, you’ll need a per‑node drag state instead of a single global flag.

Also applies to: 443-451

src/renderer/extensions/vueNodes/layout/useNodeLayout.ts (1)

1-2: Minimal layout API looks solid; assume nodeId is stable

This refactor to useNodeLayout as a thin wrapper over layoutStore.getNodeLayoutRef plus moveNodeTo is clean and keeps layout concerns in one place. The toValue(nodeIdMaybe) call at setup time assumes the node id won’t change; that’s appropriate for graph node ids but is worth keeping in mind if you ever pass a truly dynamic ref here.

Also applies to: 7-8, 30-31, 35-38, 47-48

src/renderer/core/layout/transform/useTransformState.ts (1)

55-56: Shared transform composable matches the single‑canvas design

Wrapping the previous implementation in useTransformStateIndividual() and exporting it via createSharedComposable gives you a single shared camera/transform state across all consumers, which aligns with the “one transform container per canvas” architecture. The helper refactors to named functions keep behavior identical and improve clarity/hoisting.

One thing to keep in mind: if you ever support multiple independent canvases in the same app, this singleton transform state will need to be revisited or parameterized.

Also applies to: 68-69, 95-103, 116-121, 142-146, 155-158, 161-164, 167-170, 183-189, 199-205, 216-219, 249-251

src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts (3)

33-36: Stale comment vs implementation and early startDrag semantics

In onPointerdown the comment says “Don’t start drag yet – wait for pointer move to exceed threshold”, but you already call startDrag(event, nodeId) here. That’s consistent with using pointermove to flip isDraggingVueNodes only after the threshold while still capturing the initial drag state, but the comment is now misleading.

I’d either:

  • Update the comment to reflect the actual intent (“initialize drag context now, but don’t mark as dragging until threshold”), or
  • Move startDrag into the threshold/multi‑select branches if you want the code to match the original comment.

Also applies to: 45-52, 60-65


67-79: Double startDrag call in multi‑select path

In onPointermove, the multi‑select branch (lmbDown && multiSelect && !isDragging) calls startDrag(event, nodeId) again even though startDrag was already called on pointerdown. If useNodeDrag.startDrag has any non‑idempotent side effects, this could be surprising.

Given the current design, it’s probably safe but you might want to:

  • Rely solely on the pointerdown startDrag for single‑select and multi‑select, or
  • Guard the multi‑select branch to avoid re‑calling startDrag if an active drag context for this pointer already exists.

Also applies to: 81-94


97-110: Global dragging flag and cleanup behavior

Using layoutStore.isDraggingVueNodes as the single source of truth for “any Vue node is being dragged” keeps things simple and matches the rest of the layout system, and wrapping endDrag in safeDragEnd with a finally‑style cleanup is a good defensive pattern.

Just be aware that:

  • onContextmenu intentionally skips endDrag, so any internal state in useNodeDrag relies on its own guards not to treat subsequent moves as part of the same drag.
  • onScopeDispose only resets the global isDraggingVueNodes flag; if a component unmounts mid‑drag, useNodeDrag’s internal context must be robust against that scenario.

Given the current global “single drag at a time” assumption, this is acceptable, but it’s worth revisiting if you ever allow overlapping drags.

Also applies to: 112-132, 134-152

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e2e591 and 08b0e18.

📒 Files selected for processing (19)
  • src/composables/graph/useGraphNodeManager.ts (2 hunks)
  • src/lib/litegraph/src/LGraphCanvas.ts (2 hunks)
  • src/renderer/core/layout/injectionKeys.ts (0 hunks)
  • src/renderer/core/layout/transform/TransformPane.vue (2 hunks)
  • src/renderer/core/layout/transform/useTransformState.ts (10 hunks)
  • src/renderer/core/spatial/boundsCalculator.ts (1 hunks)
  • src/renderer/extensions/minimap/data/AbstractMinimapDataSource.ts (2 hunks)
  • src/renderer/extensions/vueNodes/components/LGraphNode.vue (6 hunks)
  • src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts (7 hunks)
  • src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts (13 hunks)
  • src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts (2 hunks)
  • src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts (2 hunks)
  • src/renderer/extensions/vueNodes/interactions/resize/useNodeResize.ts (1 hunks)
  • src/renderer/extensions/vueNodes/layout/useNodeDrag.ts (1 hunks)
  • src/renderer/extensions/vueNodes/layout/useNodeLayout.ts (3 hunks)
  • tests-ui/tests/composables/element/useTransformState.test.ts (6 hunks)
  • tests-ui/tests/performance/transformPerformance.test.ts (0 hunks)
  • tests-ui/tests/renderer/extensions/vueNodes/components/LGraphNode.test.ts (2 hunks)
  • tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts (14 hunks)
💤 Files with no reviewable changes (2)
  • src/renderer/core/layout/injectionKeys.ts
  • tests-ui/tests/performance/transformPerformance.test.ts
🧰 Additional context used
🧬 Code graph analysis (10)
src/renderer/extensions/vueNodes/interactions/resize/useNodeResize.ts (1)
src/renderer/core/layout/transform/useTransformState.ts (1)
  • useTransformState (249-251)
src/composables/graph/useGraphNodeManager.ts (3)
src/lib/litegraph/src/LGraphNode.ts (1)
  • NodeId (92-92)
src/renderer/core/layout/types.ts (1)
  • NodeId (39-39)
src/lib/litegraph/src/litegraph.ts (1)
  • NodeId (114-114)
src/renderer/extensions/vueNodes/layout/useNodeDrag.ts (7)
src/renderer/core/layout/operations/layoutMutations.ts (1)
  • useLayoutMutations (67-340)
src/renderer/core/canvas/canvasStore.ts (1)
  • useCanvasStore (24-152)
src/renderer/core/layout/transform/useTransformState.ts (1)
  • useTransformState (249-251)
src/renderer/extensions/vueNodes/composables/useNodeSnap.ts (1)
  • useNodeSnap (12-73)
src/renderer/extensions/vueNodes/composables/useShiftKeySync.ts (1)
  • useShiftKeySync (25-107)
src/renderer/core/layout/store/layoutStore.ts (1)
  • layoutStore (1458-1458)
src/renderer/core/layout/types.ts (1)
  • NodeBoundsUpdate (34-37)
src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts (1)
src/lib/litegraph/src/LGraphCanvas.ts (1)
  • event (3678-3683)
src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts (3)
src/renderer/extensions/vueNodes/layout/useNodeDrag.ts (1)
  • useNodeDrag (18-18)
src/renderer/core/canvas/useCanvasInteractions.ts (1)
  • useCanvasInteractions (12-135)
src/renderer/extensions/vueNodes/utils/selectionUtils.ts (1)
  • isMultiSelectKey (8-10)
tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts (1)
src/composables/graph/useVueNodeLifecycle.ts (1)
  • useVueNodeLifecycle (182-184)
src/renderer/extensions/minimap/data/AbstractMinimapDataSource.ts (1)
src/renderer/core/spatial/boundsCalculator.ts (1)
  • PositionedNode (14-17)
src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts (1)
src/lib/litegraph/src/LGraphNode.ts (1)
  • collapsed (3420-3422)
src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts (3)
src/renderer/core/layout/types.ts (1)
  • NodeLayout (44-52)
src/renderer/core/layout/store/layoutStore.ts (3)
  • getNodeLayoutRef (204-305)
  • setSource (930-932)
  • layoutStore (1458-1458)
src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts (1)
  • useNodeEventHandlers (164-166)
src/lib/litegraph/src/LGraphCanvas.ts (1)
src/lib/litegraph/src/interfaces.ts (3)
  • IContextMenuValue (405-426)
  • IContextMenuOptions (382-403)
  • Positionable (77-119)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: collect
  • GitHub Check: test
  • GitHub Check: setup
  • GitHub Check: lint-and-format
🔇 Additional comments (12)
src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts (1)

385-395: Stopping pointermove propagation for the active pointer session is a good isolation step

The new event.stopPropagation() inside handlePointerMove, guarded by pointerSession.matches(event), cleanly isolates link-drag pointer moves from other interaction layers (e.g., node drag / Alt‑clone) without affecting unrelated pointers. This looks correct and consistent with the refactored interaction model.

src/renderer/extensions/minimap/data/AbstractMinimapDataSource.ts (2)

3-3: LGTM: Type import aligns with explicit typing below.

The import of PositionedNode supports the explicit type annotation in the getBounds method, improving type safety.


33-38: LGTM: Explicit typing improves type safety.

The explicit PositionedNode type annotation correctly maps MinimapNodeData to the format expected by calculateNodeBounds, with proper tuple construction for pos and size.

src/renderer/core/spatial/boundsCalculator.ts (1)

14-17: <!-- [scratchpad_start]
[task review_file_2/2 review_segment_1/1 verify_breaking_change resolved]
Confirmed compatibility - no breaking change

[observations]

  • LiteGraphDataSource.ts shows: node.pos[0], node.pos[1], node.size[0], node.size[1]
  • LGraphNode objects from litegraph ALREADY have pos and size as tuples/arrays
  • graphThumbnailRenderer passes graph._nodes (which are LGraphNode objects)
  • PositionedNode interface expects pos: [number, number] and size: [number, number]
  • LGraphNode already matches this structure!

[analysis]
NO BREAKING CHANGE:

  • LGraphNode objects from litegraph already have pos and size as tuple-like structures
  • AbstractMinimapDataSource converts from MinimapNodeData to PositionedNode
  • graphThumbnailRenderer passes LGraphNode directly which already satisfies PositionedNode interface
  • Type narrowing from ArrayLike to strict [number, number] doesn't break because LGraphNode.pos and .size are already exactly that

[pending]

  • None - verification complete

[done]
Confirmed all usages are compatible:

  1. AbstractMinimapDataSource: converts MinimapNodeData to PositionedNode tuples ✓
  2. graphThumbnailRenderer: passes LGraphNode[] which has pos/size as tuples ✓
  3. useMinimapViewport: imports other functions, not PositionedNode ✓
  4. Tests mock the module, so no compatibility issues ✓

All consumers are compatible with the stricter tuple types. No breaking change detected.

After verifying all usages of PositionedNode across the codebase, the type narrowing from ArrayLike<number> to [number, number] is safe:

  • AbstractMinimapDataSource: Correctly converts MinimapNodeData to PositionedNode tuples ([node.x, node.y])
  • graphThumbnailRenderer: Passes LGraphNode[] directly, which already has pos and size as tuple structures (verified via LiteGraphDataSource.ts showing node.pos[0], node.pos[1], etc.)
  • useMinimapViewport: Only imports helper functions, not PositionedNode type
  • Test mocks: Don't create incompatibilities

The stricter type accurately reflects the existing implementation without introducing breaking changes.

src/renderer/extensions/vueNodes/interactions/resize/useNodeResize.ts (1)

10-15: Typing transformState from useTransformState is a good move

Deriving the option type via ReturnType<typeof useTransformState> keeps this composable aligned with the actual transform hook API and still satisfies the existing usage (transformState.camera.z). No issues spotted.

tests-ui/tests/composables/element/useTransformState.test.ts (1)

31-37: Shared transform state and tuple typing look correct

Using a single transformState instance with per‑test syncWithCanvas keeps state predictable and aligns with the shared composable pattern. The explicit [number, number] tuple types for nodePos/nodeSize match the underlying API expectations and improve type checking without altering behavior.

Also applies to: 185-196, 212-215, 239-252

src/renderer/core/layout/transform/TransformPane.vue (1)

20-41: Refactor to shared useTransformState in TransformPane looks solid

Switching to const { camera, transformStyle, syncWithCanvas } = useTransformState() and dropping provide/inject keeps this component focused on its own needs while leveraging the shared transform state. The RAF loop guards against missing props.canvas and continues to emit transformUpdate as before, so behavior and responsibilities stay clear.

src/renderer/extensions/vueNodes/components/LGraphNode.vue (3)

140-151: Shared transform + litegraph imports look consistent

Using useTransformState() as a shared composable and importing LGraphCanvas, LGraphEventMode, and LiteGraph directly here cleanly aligns node rendering/transform state with the underlying canvas and enum usage. I don’t see issues with this wiring.

Also applies to: 156-158, 204-205


198-201: NodeId‑based right‑click handling is coherent

Switching to handleNodeRightClick(event as PointerEvent, nodeData.id) and de‑structuring it alongside other node event handlers keeps the context‑menu path consistent with the new NodeId‑centric API surface. Looks good.

Also applies to: 304-305


278-279: useNodeLayout + style bindings are straightforward

Using useNodeLayout(() => nodeData.id) for position, size, and zIndex and feeding those directly into the inline transform/zIndex style keeps DOM positioning in sync with layout state with minimal surface area (moveNodeTo used for resize). No issues spotted here.

Also applies to: 42-42

src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts (2)

30-61: LGTM! Clean refactoring to NodeId-based API.

The refactoring from VueNodeData to NodeId is well-structured. The function correctly retrieves the node from nodeManager, includes proper null checks, and preserves the multi-select and selection logic.


102-120: LGTM! Proper delegation to handleNodeSelect.

The right-click handler correctly ensures the node is selected before showing the context menu by delegating to handleNodeSelect, maintaining consistent selection behavior.

@Myestery
Copy link
Collaborator

I’ve been able to test the shift/select behaviours here ✅
Some coderabbit comments here and the tests are worth looking into

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

Updating Playwright Expectations

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts (1)

158-181: Remove or fix skipped test that expects incorrect behavior.

This test expects handleNodeSelect to be called on pointerdown, but the actual implementation only calls handleNodeSelect during pointermove when the drag threshold is exceeded. The test should either be removed (if this behavior change is intentional) or updated to match the current implementation.

Do you want me to suggest an updated test that correctly verifies the selection behavior during drag threshold detection?

🧹 Nitpick comments (2)
tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts (2)

81-86: Confirm vi.resetAllMocks behavior with your Vitest version

You switched to vi.resetAllMocks() in beforeEach. In recent Vitest versions this resets mocks back to their original implementations, but older semantics (and some docs) describe mockReset as replacing implementations with an empty function. If your environment followed the latter, module mocks like useCanvasStore or useGraphNodeManager (created via vi.mock(...)) could lose their factory behavior between tests, breaking destructuring like const { canvas } = useCanvasStore().

Given tests are passing this is probably fine, but it’s worth double‑checking your Vitest version and its resetAllMocks semantics. If you ever hit issues with mocks losing their base implementations, consider vi.clearAllMocks() here instead so that only call history is cleared while keeping implementations intact.


251-301: toggleNodeSelectionAfterPointerUp tests describe different scenarios but use identical setup

The two multi‑select tests:

  • Line 252: "deselects node that was selected at pointer down"
  • Line 264: "node not previously selected: no-op"

both currently set mockNode!.selected = true and then call toggleNodeSelectionAfterPointerUp('node-1', true). As written, the setup for “previously selected” vs “not previously selected” is identical, so the second test doesn’t clearly exercise a distinct state and can’t catch regressions that differ between those scenarios.

Consider either:

  • Adjusting the second test’s setup (e.g., start from a different mockNode.selected / selection list state, or call handleNodeSelect to simulate the pointer‑down path), or
  • Renaming the test description if both are intentionally covering the same behavior.

While you’re here, you might also use testNodeId instead of the 'node-1' literal in these calls and expectations to keep the test ID DRY.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 873d66b and 46350e8.

📒 Files selected for processing (2)
  • src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts (11 hunks)
  • tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts (14 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts (4)
src/renderer/core/layout/types.ts (1)
  • NodeLayout (44-52)
src/renderer/core/layout/store/layoutStore.ts (3)
  • getNodeLayoutRef (204-305)
  • setSource (930-932)
  • layoutStore (1458-1458)
src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts (1)
  • useNodeEventHandlers (161-163)
src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts (1)
  • useNodePointerInteractions (12-168)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: setup
  • GitHub Check: setup
  • GitHub Check: lint-and-format
  • GitHub Check: test
  • GitHub Check: collect
🔇 Additional comments (3)
src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts (2)

1-11: Good progress unskipping and updating to new API.

The test suite has been successfully updated to work with the new nodeId-based API surface, addressing the major concerns from the previous review. The imports now reflect the new dependencies (Pinia testing setup, layoutStore, useNodeDrag, etc.), and the suite is no longer fully skipped. However, one test remains skipped on line 158 that needs attention.


1-324: Test suite successfully updated to new API surface.

This test file has been significantly improved to address the concerns from the previous review:

  • ✅ Test suite is no longer fully skipped (only one test remains skipped)
  • ✅ Mocks updated to reflect new API: useNodeDrag, useNodeEventHandlers, layoutStore
  • ✅ Tests now use nodeId strings ('test-node-123') instead of VueNodeData objects
  • ✅ Tests verify interactions with layoutStore.isDraggingVueNodes for drag state management
  • ✅ Event handlers now receive node IDs as parameters matching the new implementation

The remaining issues (explicit state reset, one skipped test, misleading comments) are minor and can be addressed as follow-ups.

Based on learnings

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

52-64: Type-only GraphNodeManager import in mock is correct and keeps runtime clean

Using import type { GraphNodeManager } and restricting it to type assertions inside the useGraphNodeManager mock avoids adding runtime dependencies while still giving good typing for nodeManager. The mock structure (returning a shallowRef with getNodemockNode) looks consistent with how useNodeEventHandlers is exercised in the tests.

@github-actions github-actions bot removed the New Browser Test Expectations New browser test screenshot should be set by github action label Nov 21, 2025
@DrJKL DrJKL disabled auto-merge November 21, 2025 22:15
@DrJKL DrJKL merged commit 9da82f4 into main Nov 21, 2025
3 checks passed
@DrJKL DrJKL deleted the drjkl/the-prestige branch November 21, 2025 22:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants