Skip to content

Conversation

@AustinMroz
Copy link
Collaborator

@AustinMroz AustinMroz commented Nov 21, 2025

  • On graph change, set the graph.id as location hash
  • Use window.onhashchange to navigate to the target graph.id either in the current, or any other loaded workflow.

canvasStore.currentGraph does not trigger when app.loadGraphData is called. A trigger could be forced here, but I'm concerned about side effects. Instead updateHash is manually called.

Code search shows that there are no current custom nodes using onhashchange

┆Issue is synchronized with this Notion page by Unito

@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Nov 21, 2025
@github-actions
Copy link

github-actions bot commented Nov 21, 2025

🎭 Playwright Test Results

🕵🏻 No test results found

⏰ Completed at: 11/22/2025, 04:58:48 PM UTC

📊 Test Reports by Browser

  • chromium: Deployment failed
  • chromium-2x: Deployment failed
  • chromium-0.5x: Deployment failed
  • mobile-chrome: Deployment failed

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

@github-actions
Copy link

github-actions bot commented Nov 21, 2025

🎨 Storybook Build Status

Build failed!

⏰ Completed at: 11/22/2025, 04:57:34 PM UTC

🔗 Links


⚠️ Please check the workflow logs for error details.

@github-actions
Copy link

Bundle Size Report

Summary

  • Raw size: 13.7 MB baseline 13.7 MB — 🔴 +1.78 kB
  • Gzip: 2.76 MB baseline 2.76 MB — 🔴 +429 B
  • Brotli: 2.16 MB baseline 2.16 MB — 🔴 +264 B
  • Bundles: 92 current • 92 baseline • 38 added / 38 removed

Category Glance
App Entry Points 🔴 +1.78 kB (3.13 MB) · Vendor & Third-Party ⚪ 0 B (5.32 MB) · Other ⚪ 0 B (3.87 MB) · Graph Workspace ⚪ 0 B (945 kB) · 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) • 🔴 +1.78 kB

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-UBcd71i6.js (new) 2.77 MB 🔴 +2.77 MB 🔴 +579 kB 🔴 +440 kB
assets/index-BbR_fJTy.js (removed) 2.77 MB 🟢 -2.77 MB 🟢 -578 kB 🟢 -440 kB
assets/index-bOmVPMTE.js (removed) 364 kB 🟢 -364 kB 🟢 -75.2 kB 🟢 -61.4 kB
assets/index-DOzIe-Q4.js (new) 364 kB 🔴 +364 kB 🔴 +75.2 kB 🔴 +61.3 kB
assets/index-CW9PzUCV.js (new) 345 B 🔴 +345 B 🔴 +245 B 🔴 +234 B
assets/index-DvdrFyIX.js (removed) 345 B 🟢 -345 B 🟢 -244 B 🟢 -202 B

Status: 3 added / 3 removed

Graph Workspace — 945 kB (baseline 945 kB) • ⚪ 0 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-BG8DJ6v-.js (new) 945 kB 🔴 +945 kB 🔴 +183 kB 🔴 +140 kB
assets/GraphView-Chd4AFl_.js (removed) 945 kB 🟢 -945 kB 🟢 -183 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-C-HkzBkh.js (new) 7.97 kB 🔴 +7.97 kB 🔴 +2.43 kB 🔴 +2.14 kB
assets/UserSelectView-COXO2z2o.js (removed) 7.97 kB 🟢 -7.97 kB 🟢 -2.43 kB 🟢 -2.13 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-CF4I5Avb.js (new) 22.9 kB 🔴 +22.9 kB 🔴 +5.46 kB 🔴 +4.79 kB
assets/CreditsPanel-yLpKByEJ.js (removed) 22.9 kB 🟢 -22.9 kB 🟢 -5.46 kB 🟢 -4.79 kB
assets/KeybindingPanel-CEhrZE9k.js (removed) 15.1 kB 🟢 -15.1 kB 🟢 -3.73 kB 🟢 -3.29 kB
assets/KeybindingPanel-YBIXQuqw.js (new) 15.1 kB 🔴 +15.1 kB 🔴 +3.73 kB 🔴 +3.29 kB
assets/ExtensionPanel-D_LW6rI0.js (removed) 11.9 kB 🟢 -11.9 kB 🟢 -2.79 kB 🟢 -2.45 kB
assets/ExtensionPanel-Djqutjsf.js (new) 11.9 kB 🔴 +11.9 kB 🔴 +2.79 kB 🔴 +2.45 kB
assets/AboutPanel-BXTLNXLA.js (new) 10.1 kB 🔴 +10.1 kB 🔴 +2.62 kB 🔴 +2.31 kB
assets/AboutPanel-Cl_GfZaP.js (removed) 10.1 kB 🟢 -10.1 kB 🟢 -2.62 kB 🟢 -2.31 kB
assets/ServerConfigPanel-cqI78NLZ.js (removed) 8.02 kB 🟢 -8.02 kB 🟢 -2.12 kB 🟢 -1.88 kB
assets/ServerConfigPanel-HuNG-8Q1.js (new) 8.02 kB 🔴 +8.02 kB 🔴 +2.12 kB 🔴 +1.87 kB
assets/UserPanel-UjdO38Re.js (new) 7.74 kB 🔴 +7.74 kB 🔴 +2.03 kB 🔴 +1.77 kB
assets/UserPanel-zWxYp9j5.js (removed) 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-a55o33KB.js (new) 53.9 kB 🔴 +53.9 kB 🔴 +8.43 kB 🔴 +7.24 kB
assets/Load3D.vue_vue_type_script_setup_true_lang-rJBVPrzE.js (removed) 53.9 kB 🟢 -53.9 kB 🟢 -8.43 kB 🟢 -7.23 kB
assets/WidgetSelect.vue_vue_type_script_setup_true_lang-CfUokXvm.js (removed) 48.1 kB 🟢 -48.1 kB 🟢 -10.3 kB 🟢 -8.96 kB
assets/WidgetSelect.vue_vue_type_script_setup_true_lang-zK4EO2wD.js (new) 48.1 kB 🔴 +48.1 kB 🔴 +10.3 kB 🔴 +8.93 kB
assets/WidgetInputNumber.vue_vue_type_script_setup_true_lang-CqCcHvMN.js (new) 12.7 kB 🔴 +12.7 kB 🔴 +3.31 kB 🔴 +2.94 kB
assets/WidgetInputNumber.vue_vue_type_script_setup_true_lang-USfR3vGy.js (removed) 12.7 kB 🟢 -12.7 kB 🟢 -3.3 kB 🟢 -2.92 kB
assets/ComfyQueueButton-BdXeX0GO.js (new) 9.22 kB 🔴 +9.22 kB 🔴 +2.5 kB 🔴 +2.2 kB
assets/ComfyQueueButton-CO5gSuMT.js (removed) 9.22 kB 🟢 -9.22 kB 🟢 -2.5 kB 🟢 -2.2 kB
assets/WidgetLayoutField.vue_vue_type_script_setup_true_lang-C7ONxcko.js (removed) 2.14 kB 🟢 -2.14 kB 🟢 -795 B 🟢 -695 B
assets/WidgetLayoutField.vue_vue_type_script_setup_true_lang-NsGF_G0t.js (new) 2.14 kB 🔴 +2.14 kB 🔴 +795 B 🔴 +694 B
assets/MediaTitle.vue_vue_type_script_setup_true_lang-DgyLrjqD.js (new) 848 B 🔴 +848 B 🔴 +474 B 🔴 +417 B
assets/MediaTitle.vue_vue_type_script_setup_true_lang-Dtvav2K4.js (removed) 848 B 🟢 -848 B 🟢 -475 B 🟢 -413 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-CnRnwpHz.js (new) 7.51 kB 🔴 +7.51 kB 🔴 +1.84 kB 🔴 +1.58 kB
assets/keybindingService-tGvMqxzD.js (removed) 7.51 kB 🟢 -7.51 kB 🟢 -1.83 kB 🟢 -1.58 kB
assets/audioService-CWBSb7Vt.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -960 B 🟢 -819 B
assets/audioService-DN2ufDR8.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +962 B 🔴 +827 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-B3laTuMG.js (removed) 1.41 kB 🟢 -1.41 kB 🟢 -650 B 🟢 -550 B
assets/audioUtils-DtGNDyza.js (new) 1.41 kB 🔴 +1.41 kB 🔴 +650 B 🔴 +547 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-BbEtjSvB.js (new) 21.9 kB 🔴 +21.9 kB 🔴 +5.52 kB 🔴 +4.88 kB
assets/WidgetRecordAudio-C2DqU4_U.js (removed) 21.9 kB 🟢 -21.9 kB 🟢 -5.52 kB 🟢 -4.87 kB
assets/AudioPreviewPlayer-D11jpYYU.js (new) 14.9 kB 🔴 +14.9 kB 🔴 +3.69 kB 🔴 +3.3 kB
assets/AudioPreviewPlayer-DgDaZV6y.js (removed) 14.9 kB 🟢 -14.9 kB 🟢 -3.69 kB 🟢 -3.29 kB
assets/WidgetGalleria-B79lW0Om.js (new) 5.56 kB 🔴 +5.56 kB 🔴 +1.74 kB 🔴 +1.54 kB
assets/WidgetGalleria-BYez8HQB.js (removed) 5.56 kB 🟢 -5.56 kB 🟢 -1.75 kB 🟢 -1.54 kB
assets/WidgetColorPicker-DbQL8FwC.js (new) 4.87 kB 🔴 +4.87 kB 🔴 +1.69 kB 🔴 +1.48 kB
assets/WidgetColorPicker-uNA3JldQ.js (removed) 4.87 kB 🟢 -4.87 kB 🟢 -1.69 kB 🟢 -1.48 kB
assets/WidgetMarkdown-DTx85H8J.js (new) 4.6 kB 🔴 +4.6 kB 🔴 +1.6 kB 🔴 +1.4 kB
assets/WidgetMarkdown-j1OWXnXF.js (removed) 4.6 kB 🟢 -4.6 kB 🟢 -1.6 kB 🟢 -1.4 kB
assets/WidgetAudioUI-3YLVmiSF.js (new) 4.33 kB 🔴 +4.33 kB 🔴 +1.44 kB 🔴 +1.29 kB
assets/WidgetAudioUI-BWMPNYIC.js (removed) 4.33 kB 🟢 -4.33 kB 🟢 -1.44 kB 🟢 -1.28 kB
assets/WidgetTextarea-BdkN9uLd.js (removed) 3.72 kB 🟢 -3.72 kB 🟢 -1.3 kB 🟢 -1.14 kB
assets/WidgetTextarea-xK0bDFVt.js (new) 3.72 kB 🔴 +3.72 kB 🔴 +1.3 kB 🔴 +1.15 kB
assets/WidgetInputText-BdUOmSjN.js (new) 3.45 kB 🔴 +3.45 kB 🔴 +1.24 kB 🔴 +1.09 kB
assets/WidgetInputText-DEf5r33W.js (removed) 3.45 kB 🟢 -3.45 kB 🟢 -1.23 kB 🟢 -1.09 kB
assets/WidgetToggleSwitch-BsdCQvvO.js (removed) 3.23 kB 🟢 -3.23 kB 🟢 -1.14 kB 🟢 -1.01 kB
assets/WidgetToggleSwitch-DyuoJF2X.js (new) 3.23 kB 🔴 +3.23 kB 🔴 +1.14 kB 🔴 +1.01 kB
assets/MediaImageBottom-pnUwaBVv.js (removed) 3.05 kB 🟢 -3.05 kB 🟢 -1.05 kB 🟢 -907 B
assets/MediaImageBottom-zWLhkfau.js (new) 3.05 kB 🔴 +3.05 kB 🔴 +1.05 kB 🔴 +918 B
assets/MediaAudioBottom-D7sHxyvy.js (removed) 3 kB 🟢 -3 kB 🟢 -1.05 kB 🟢 -927 B
assets/MediaAudioBottom-JFC9p1tZ.js (new) 3 kB 🔴 +3 kB 🔴 +1.05 kB 🔴 +932 B
assets/Media3DTop-Bets-6AV.js (removed) 3 kB 🟢 -3 kB 🟢 -1.08 kB 🟢 -924 B
assets/Media3DTop-dnZyr_rv.js (new) 3 kB 🔴 +3 kB 🔴 +1.08 kB 🔴 +913 B
assets/MediaVideoBottom-C6ZKf9TW.js (removed) 3 kB 🟢 -3 kB 🟢 -1.05 kB 🟢 -924 B
assets/MediaVideoBottom-n5b8KnBQ.js (new) 3 kB 🔴 +3 kB 🔴 +1.05 kB 🔴 +927 B
assets/Media3DBottom-BsaWEu_w.js (removed) 2.98 kB 🟢 -2.98 kB 🟢 -1.04 kB 🟢 -909 B
assets/Media3DBottom-DigUakcu.js (new) 2.98 kB 🔴 +2.98 kB 🔴 +1.04 kB 🔴 +911 B
assets/WidgetSelect-DaMn9ADU.js (new) 2.17 kB 🔴 +2.17 kB 🔴 +674 B 🔴 +576 B
assets/WidgetSelect-DEG6g_TG.js (removed) 2.17 kB 🟢 -2.17 kB 🟢 -674 B 🟢 -577 B
assets/WidgetInputNumber-Igtq8ttN.js (removed) 2.12 kB 🟢 -2.12 kB 🟢 -662 B 🟢 -552 B
assets/WidgetInputNumber-rwUwswuk.js (new) 2.12 kB 🔴 +2.12 kB 🔴 +663 B 🔴 +561 B
assets/Load3D-BkJZEHA0.js (removed) 1.94 kB 🟢 -1.94 kB 🟢 -598 B 🟢 -536 B
assets/Load3D-fHAajd5I.js (new) 1.94 kB 🔴 +1.94 kB 🔴 +597 B 🔴 +499 B
assets/WidgetLegacy-C7UUTOio.js (new) 1.88 kB 🔴 +1.88 kB 🔴 +558 B 🔴 +469 B
assets/WidgetLegacy-Dn7PmlP6.js (removed) 1.88 kB 🟢 -1.88 kB 🟢 -561 B 🟢 -507 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

@AustinMroz AustinMroz marked this pull request as draft November 21, 2025 20:18
//Allow navigation with forward/back buttons
//TODO: Extend for dialogues?
//TODO: force update widget.promoted
function onHashChange() {
Copy link
Contributor

Choose a reason for hiding this comment

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

How about on page reload? I.e., initial load

@christian-byrne
Copy link
Contributor

Would consider using https://vueuse.org/router/useRouteHash/

@AustinMroz
Copy link
Collaborator Author

Would consider using https://vueuse.org/router/useRouteHash/

Already got pinged by Alex for this and made the change. Will push once I fix your suggestion of making navigation work on initial load.

@coderabbitai
Copy link

coderabbitai bot commented Nov 22, 2025

📝 Walkthrough

Walkthrough

The changes implement hash-based URL navigation for subgraphs with browser back/forward button support. A new routing dependency is added, the subgraph navigation store is extended with hash synchronization logic and workflow integration, and a WebSocket fixture is updated to exclude URL fragments from connection URLs.

Changes

Cohort / File(s) Summary
Route-based navigation support
src/stores/subgraphNavigationStore.ts, src/scripts/app.ts, src/components/breadcrumb/SubgraphBreadcrumbItem.vue
Added hash-based navigation with forward/back button handling. Introduced navigateToHash and updateHash mechanisms to synchronize subgraph state with URL hash. Integrated workflow opening when navigating to subgraphs within open workflows. Added guards (blockHashUpdate, initialLoad) to prevent recursive hash updates. Removed href="#" attribute from breadcrumb anchor element. Called updateHash() after loading new graphs.
Dependency addition
package.json
Added @vueuse/router (^14.0.0) as a runtime dependency.
WebSocket URL construction
browser_tests/fixtures/ws.ts
Modified WebSocket URL generation to remove URL fragments (split at '#') before applying protocol and path.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Router
    participant SubgraphNav as SubgraphNav Store
    participant Workflow as Workflow Service
    participant App

    rect rgb(200, 220, 255)
    Note over Browser,App: Initial Load
    App->>SubgraphNav: updateHash()
    SubgraphNav->>Router: Get current hash
    SubgraphNav->>SubgraphNav: Set initialLoad = true
    end

    rect rgb(220, 240, 220)
    Note over Browser,App: Hash Navigation (back/forward or manual)
    Browser->>Router: Hash changes
    Router->>SubgraphNav: Route hash updated
    SubgraphNav->>SubgraphNav: Check blockHashUpdate guard
    alt blockHashUpdate is false
        SubgraphNav->>Workflow: Check if subgraph in open workflow
        alt Subgraph in open workflow
            SubgraphNav->>Workflow: Open corresponding workflow
            Note over Workflow: Wait for completion
        end
        SubgraphNav->>SubgraphNav: Navigate to target subgraph
    end
    end

    rect rgb(240, 220, 220)
    Note over Browser,App: Internal Navigation (user clicks)
    SubgraphNav->>SubgraphNav: Set blockHashUpdate = true
    SubgraphNav->>SubgraphNav: Update navigationStack
    SubgraphNav->>SubgraphNav: Call updateHash()
    SubgraphNav->>Router: Push/replace hash
    SubgraphNav->>SubgraphNav: Set blockHashUpdate = false
    end
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/thumb-navigation

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

@AustinMroz AustinMroz marked this pull request as ready for review November 22, 2025 17:20
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: 0

🧹 Nitpick comments (4)
src/scripts/app.ts (1)

60-60: New subgraphNavigationStore usage introduces a circular import but is currently safe

Importing useSubgraphNavigationStore here while src/stores/subgraphNavigationStore.ts already imports { app } creates a module cycle, but since useSubgraphNavigationStore().updateHash() is only called from the ComfyApp instance method (after app is constructed), this shouldn’t break initialization.

If we ever start using useSubgraphNavigationStore() at module top‑level elsewhere, it’d be worth revisiting this and possibly extracting hash-sync concerns into a smaller helper to avoid tighter coupling between app.ts and the store. For now, the placement of updateHash() after afterLoadNewGraph() looks appropriate and keeps the URL in sync with the loaded graph.

Also applies to: 1309-1310

browser_tests/fixtures/ws.ts (1)

30-36: Hash stripping for WebSocket URL is correct; consider simplifying URL construction

Dropping the fragment via window.location.toString().split('#')[0] avoids producing invalid URLs like #graphIdws once graph IDs live in the hash, which is the right direction here.

If you want this fixture to be more explicit and robust, you could construct the WS URL directly from window.location.origin, clearing search/hash instead of relying on string concat:

-              if (!url) {
-                // If no URL specified, use page URL
-                const u = new URL(window.location.toString().split('#')[0])
-                u.protocol = 'ws:'
-                u.pathname = '/'
-                url = u.toString() + 'ws'
-              }
+              if (!url) {
+                // If no URL specified, connect to `${origin}/ws`
+                const u = new URL(window.location.origin)
+                u.protocol = 'ws:'
+                u.pathname = '/ws'
+                u.search = ''
+                u.hash = ''
+                url = u.toString()
+              }

Not mandatory, but it avoids odd query strings like ?foo=barws if tests ever run with query parameters.

src/stores/subgraphNavigationStore.ts (2)

161-227: Hash navigation logic: fix operator precedence, initial-load race, and hash normalization

The overall approach (using useRouteHash, syncing canvas graph ↔ URL hash, and guarding with blockHashUpdate/initialLoad) looks solid, but there are a few concrete issues and edge cases worth tightening:

  1. Operator precedence bug in router.replace(...) (Line 210)

    router.replace('#' + window.location.hash.slice(1) || app.graph.id)

    Because + has higher precedence than ||, this always evaluates as:

    router.replace(('#' + window.location.hash.slice(1)) || app.graph.id)

    so the fallback to app.graph.id never runs. On an initial load with no hash, this produces '#' instead of '#<graphId>', and later the newId === (currentId || app.graph.id) check prevents correcting it.

    Suggestion:

  •  if (!routeHash.value) {
    
  •    router.replace('#' + window.location.hash.slice(1) || app.graph.id)
    
  •  if (!routeHash.value) {
    
  •    const raw = window.location.hash.slice(1) || app.graph.id
    
  •    router.replace({ hash: `#${raw}` })
     }
    
    
    Using the object form also aligns better with vue-router’s documented API.  
    
    
    
  1. Potential race on initial load when hash targets another workflow (Lines 211–216)
    In the initialLoad branch:

    initialLoad = false
    navigateToHash(routeHash.value)
    const graph = canvasStore.getCanvas().graph
    if (isSubgraph(graph)) workflowStore.activeSubgraph = graph

    navigateToHash is async (it awaits useWorkflowService().openWorkflow(workflow)), but it’s not awaited here. If the hash points into a different workflow, graph can still be the old workflow’s root when you read it, so activeSubgraph may never be updated for the target subgraph.

    Two options to avoid the race:

    • Make updateHash async and await navigateToHash in the initial-load branch, or
    • Move the activeSubgraph update into navigateToHash after canvas.setGraph(targetGraph) in both code paths (same-workflow and cross-workflow), so it always runs once navigation completes.

    Either approach keeps workflowStore.activeSubgraph consistent with the visible graph.

  2. Hash normalization in navigateToHash (Line 169)

    const locatorId = newHash?.slice(1) ?? root.id

    This assumes newHash always includes a leading #. Today that’s probably true for your useRouteHash() + vue-router setup, but if useRouteHash ever returns a bare id (or its behavior changes), you’d silently drop the first character.

    A more defensive version that works with both '#id' and 'id':

  • async function navigateToHash(newHash: string | undefined | null) {
  •  const root = app.graph
    
  •  const locatorId = newHash?.slice(1) ?? root.id
    
  • async function navigateToHash(newHash: string | undefined | null) {
  •  const root = app.graph
    
  •  const locatorId = newHash
    
  •    ? newHash.startsWith('#')
    
  •      ? newHash.slice(1)
    
  •      : newHash
    
  •    : root.id
    
    
    
    
    
  1. Optional: guard against missing canvas (Lines 171, 214, 218)
    Both navigateToHash and updateHash call canvasStore.getCanvas() and immediately dereference .graph. If this store is ever instantiated before the canvas is ready (or in tests), that could throw. A simple guard would make it safer:

    const canvas = canvasStore.getCanvas()
    if (!canvas) return

    Not strictly required if you know the store is only used after setup, but low-cost defensive coding.


235-236: Exposing updateHash from the store is useful; consider documenting its intended call sites

Returning updateHash here to be called from app.loadGraphData is a good way to handle the “workflow load doesn’t emit canvasStore.currentGraph changes” gap.

Given it has some internal state (blockHashUpdate, initialLoad), it may be worth a brief JSDoc comment near the function saying it’s meant to be invoked:

  • After a new graph is loaded (e.g., afterLoadNewGraph), and
  • Not during internal hash-driven navigation (where blockHashUpdate suppresses it).

That will help future maintainers avoid calling it in the wrong phase and accidentally fighting the router/watchers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff0d385 and e4774ca.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • browser_tests/fixtures/ws.ts (1 hunks)
  • package.json (1 hunks)
  • src/components/breadcrumb/SubgraphBreadcrumbItem.vue (0 hunks)
  • src/scripts/app.ts (2 hunks)
  • src/stores/subgraphNavigationStore.ts (3 hunks)
💤 Files with no reviewable changes (1)
  • src/components/breadcrumb/SubgraphBreadcrumbItem.vue
🧰 Additional context used
🧬 Code graph analysis (2)
src/scripts/app.ts (1)
src/stores/subgraphNavigationStore.ts (1)
  • useSubgraphNavigationStore (21-239)
src/stores/subgraphNavigationStore.ts (5)
src/scripts/app.ts (2)
  • app (1710-1710)
  • graph (161-163)
src/platform/workflow/management/stores/workflowStore.ts (1)
  • activeState (60-62)
src/lib/litegraph/src/LGraph.ts (1)
  • subgraphs (383-385)
src/platform/workflow/core/services/workflowService.ts (1)
  • useWorkflowService (22-413)
src/utils/typeGuardUtil.ts (1)
  • isSubgraph (18-20)
🪛 ESLint
src/stores/subgraphNavigationStore.ts

[error] 4-4: Unable to resolve path to module '@vueuse/router'.

(import-x/no-unresolved)


[error] 8-8: Unable to resolve path to module '@/platform/workflow/management/stores/workflowStore'.

(import-x/no-unresolved)


[error] 9-9: Unable to resolve path to module '@/platform/workflow/core/services/workflowService'.

(import-x/no-unresolved)


[error] 10-10: Unable to resolve path to module '@/renderer/core/canvas/canvasStore'.

(import-x/no-unresolved)


[error] 11-11: Unable to resolve path to module '@/router'.

(import-x/no-unresolved)


[error] 12-12: Unable to resolve path to module '@/scripts/app'.

(import-x/no-unresolved)


[error] 13-13: Unable to resolve path to module '@/utils/graphTraversalUtil'.

(import-x/no-unresolved)


[error] 14-14: Unable to resolve path to module '@/utils/typeGuardUtil'.

(import-x/no-unresolved)

🔇 Additional comments (1)
package.json (1)

152-155: Ensure @vueuse/router version is compatible with your existing VueUse / router stack

Adding @vueuse/router as ^14.0.0 looks fine, but since @vueuse/core and vue-router are pulled in via the catalog: indirection, please double‑check that:

  • The @vueuse/router version you get is intended to pair with the @vueuse/core version from your catalog.
  • The workspace lockfile has been updated (pnpm install) so tools like eslint/import‑x no longer report no-unresolved on @vueuse/router.

@DrJKL DrJKL added the claude-review Add to trigger a PR code review from Claude Code label Nov 22, 2025
@@ -1,2 +1,2 @@
<template>
<a
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be a RouterLink?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

From quick testing, it wouldn't be a quick substitution. It's causing errors on click about missing state.

Perhaps we move in the opposite direction and replace the with a

since it's not actually performing a navigation?

//Allow navigation with forward/back buttons
//TODO: Extend for dialogues?
//TODO: force update widget.promoted
async function navigateToHash(newHash: string | undefined | null) {
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 you're doing a lot imperatively here that could be done through Route definitions and use of the Router, but I don't know for sure how easy it would be to transition to that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My understanding is that it would be particularly hard. The navigation to a new graph happens before any event listeners or watched values change. The correct router based approach would mean initiating a navigation to the new address which causes the new graph or subgraph to load.

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:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants