Skip to content

Still Draft Feature: ECS Migration - #14246

Open
DrJKL wants to merge 25 commits into
mainfrom
feature/ecs-migration
Open

Still Draft Feature: ECS Migration#14246
DrJKL wants to merge 25 commits into
mainfrom
feature/ecs-migration

Conversation

@DrJKL

@DrJKL DrJKL commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Still-draft feature branch for migrating entity state from LiteGraph objects and runtime mirrors into dedicated stores with derived, reactive views.

This consolidates the reviewed ECS migration slices, strengthens their coverage, incorporates the layout-state follow-up, and documents the current migration status and remaining work.

Changes

  • Move widget values and node shell state into dedicated stores.
  • Make link topology the source of truth for connectivity.
  • Store reroute chains while deriving membership and floating-link state.
  • Derive widgeted input connectivity, badges, and promoted widget rows from store-backed state.
  • Remove the input and output runtime link mirrors.
  • Keep DOM measurements separate from persisted node layout state.
  • Add migration coverage, extension guidance, and architecture/status documentation.

Reviewed PRs merged into this feature branch:

Review Focus

  • Store ownership and synchronization boundaries.
  • Serialization, undo/redo, subgraph, and extension compatibility.
  • Whether remaining direct entity state should migrate before this feature leaves draft.

Status

Still draft. The branch is rebased onto main; CI is running against the rebased history.

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

🎭 Playwright: ✅ 1993 passed, 0 failed

📊 Browser Reports
  • chromium: View Report (✅ 1972 / ❌ 0 / ⚠️ 0 / ⏭️ 5)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 18 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • New-test walkthrough (chromium, recorded video): View Report

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 08/22/2026, 08:13:21 AM UTC

Links

📦 Bundle: 9.11 MB gzip 🟢 -1.88 kB

Details

Summary

  • Raw size: 38.6 MB baseline 38.6 MB — 🟢 -24.2 kB
  • Gzip: 9.11 MB baseline 9.11 MB — 🟢 -1.88 kB
  • Brotli: 6.37 MB baseline 6.37 MB — 🔴 +930 B
  • Bundles: 439 current • 438 baseline • 230 added / 229 removed

Category Glance
Vendor & Third-Party 🟢 -55.4 kB (18 MB) · UI Components 🔴 +44.9 kB (112 kB) · Data & Services 🟢 -34.1 kB (3.49 MB) · Graph Workspace 🔴 +20.1 kB (1.39 MB) · Other 🔴 +231 B (14.1 MB) · Utilities & Hooks 🟢 -20 B (549 kB) · + 5 more

App Entry Points — 3.71 kB (baseline 3.71 kB) • 🟢 -2 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-Drb4FD7k.js (removed) 3.71 kB 🟢 -3.71 kB 🟢 -1.86 kB 🟢 -1.62 kB
assets/index-BxvIpMJN.js (new) 3.71 kB 🔴 +3.71 kB 🔴 +1.85 kB 🔴 +1.58 kB

Status: 1 added / 1 removed

Graph Workspace — 1.39 MB (baseline 1.37 MB) • 🔴 +20.1 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-lgAFnuRO.js (new) 1.39 MB 🔴 +1.39 MB 🔴 +304 kB 🔴 +228 kB
assets/GraphView-ByjbvPKu.js (removed) 1.37 MB 🟢 -1.37 MB 🟢 -298 kB 🟢 -224 kB
assets/WidgetCompositor-EkZ4CQ5t.js (new) 8.17 kB 🔴 +8.17 kB 🔴 +2.76 kB 🔴 +2.46 kB
assets/WidgetCompositor-nMIhEDQG.js (removed) 8.17 kB 🟢 -8.17 kB 🟢 -2.76 kB 🟢 -2.46 kB

Status: 2 added / 2 removed / 1 unchanged

Views & Navigation — 124 kB (baseline 124 kB) • 🔴 +2 B

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-DlIszcuL.js (removed) 25 kB 🟢 -25 kB 🟢 -6.25 kB 🟢 -5.53 kB
assets/CloudSurveyView-DQW91fLk.js (new) 25 kB 🔴 +25 kB 🔴 +6.25 kB 🔴 +5.53 kB
assets/CloudLayoutView-DIrCUBCc.js (removed) 21.8 kB 🟢 -21.8 kB 🟢 -6.56 kB 🟢 -5.74 kB
assets/CloudLayoutView-NoMn84dB.js (new) 21.8 kB 🔴 +21.8 kB 🔴 +6.56 kB 🔴 +5.74 kB
assets/OAuthConsentView-BWW0S2F0.js (removed) 15.5 kB 🟢 -15.5 kB 🟢 -4.25 kB 🟢 -3.71 kB
assets/OAuthConsentView-CYP2U4-D.js (new) 15.5 kB 🔴 +15.5 kB 🔴 +4.25 kB 🔴 +3.71 kB
assets/UserCheckView-Crfi359D.js (new) 8.75 kB 🔴 +8.75 kB 🔴 +2.19 kB 🔴 +1.9 kB
assets/UserCheckView-DDq5qOEi.js (removed) 8.75 kB 🟢 -8.75 kB 🟢 -2.19 kB 🟢 -1.9 kB
assets/CloudLoginView-HEG_sVYu.js (new) 8.74 kB 🔴 +8.74 kB 🔴 +2.55 kB 🔴 +2.25 kB
assets/CloudLoginView-CH_YabHW.js (removed) 8.74 kB 🟢 -8.74 kB 🟢 -2.55 kB 🟢 -2.26 kB
assets/useCloudAuthPage-B0etcoIb.js (new) 7.08 kB 🔴 +7.08 kB 🔴 +2.51 kB 🔴 +2.2 kB
assets/useCloudAuthPage-Bhc5Vnok.js (removed) 7.08 kB 🟢 -7.08 kB 🟢 -2.51 kB 🟢 -2.19 kB
assets/CloudSignupView-BAlvVqsf.js (removed) 6.96 kB 🟢 -6.96 kB 🟢 -2.28 kB 🟢 -2 kB
assets/CloudSignupView-BPc82ycs.js (new) 6.96 kB 🔴 +6.96 kB 🔴 +2.28 kB 🔴 +2 kB
assets/WidgetTextPreview-DVRe3i7k.js (removed) 6.07 kB 🟢 -6.07 kB 🟢 -2.13 kB 🟢 -1.9 kB
assets/WidgetTextPreview-Cg8CbYzd.js (new) 6.07 kB 🔴 +6.07 kB 🔴 +2.13 kB 🔴 +1.9 kB
assets/CloudSubscriptionRedirectView-C2Sl7xkI.js (new) 6.05 kB 🔴 +6.05 kB 🔴 +2.25 kB 🔴 +1.96 kB
assets/CloudSubscriptionRedirectView-CwjqCArg.js (removed) 6.05 kB 🟢 -6.05 kB 🟢 -2.25 kB 🟢 -1.96 kB
assets/UserSelectView-kDHQK1bz.js (new) 5.49 kB 🔴 +5.49 kB 🔴 +1.96 kB 🔴 +1.71 kB
assets/UserSelectView-KZJpH7rI.js (removed) 5.49 kB 🟢 -5.49 kB 🟢 -1.96 kB 🟢 -1.71 kB
assets/CloudForgotPasswordView-Cc4QGej2.js (new) 4.97 kB 🔴 +4.97 kB 🔴 +1.72 kB 🔴 +1.49 kB
assets/CloudForgotPasswordView-dd7ov6Ca.js (removed) 4.97 kB 🟢 -4.97 kB 🟢 -1.72 kB 🟢 -1.49 kB
assets/CloudAuthTimeoutView-BoNqV1hQ.js (new) 4.43 kB 🔴 +4.43 kB 🔴 +1.54 kB 🔴 +1.34 kB
assets/CloudAuthTimeoutView-DgIOmbBm.js (removed) 4.43 kB 🟢 -4.43 kB 🟢 -1.54 kB 🟢 -1.34 kB
assets/OAuthLayoutView-DCuB7T0e.js (new) 1.31 kB 🔴 +1.31 kB 🔴 +701 B 🔴 +592 B
assets/OAuthLayoutView-DDosDj2s.js (removed) 1.31 kB 🟢 -1.31 kB 🟢 -699 B 🟢 -586 B
assets/layout-BwkDZ0X8.js (new) 403 B 🔴 +403 B 🔴 +251 B 🔴 +211 B
assets/layout-CjRRJror.js (removed) 403 B 🟢 -403 B 🟢 -251 B 🟢 -205 B
assets/WidgetTextPreview-BClyrABg.js (removed) 131 B 🟢 -131 B 🟢 -100 B 🟢 -84 B
assets/WidgetTextPreview-DO_HB6mq.js (new) 131 B 🔴 +131 B 🔴 +100 B 🔴 +93 B

Status: 15 added / 15 removed / 2 unchanged

Panels & Settings — 591 kB (baseline 591 kB) • 🟢 -5 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-DhbKDkV2.js (new) 49.4 kB 🔴 +49.4 kB 🔴 +9.95 kB 🔴 +8.8 kB
assets/KeybindingPanel-DIA1Y2ZK.js (removed) 49.4 kB 🟢 -49.4 kB 🟢 -9.94 kB 🟢 -8.79 kB
assets/CreditsPanel-CF9Sb0H3.js (removed) 36.6 kB 🟢 -36.6 kB 🟢 -8.9 kB 🟢 -7.79 kB
assets/CreditsPanel-DfoiCuot.js (new) 36.6 kB 🔴 +36.6 kB 🔴 +8.9 kB 🔴 +7.79 kB
assets/SecretsPanel-CIgS1nvs.js (new) 33.7 kB 🔴 +33.7 kB 🔴 +7.88 kB 🔴 +6.91 kB
assets/SecretsPanel-BzicXPui.js (removed) 33.7 kB 🟢 -33.7 kB 🟢 -7.88 kB 🟢 -6.9 kB
assets/AboutPanel-lC2OEVIq.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.12 kB 🟢 -2.81 kB
assets/AboutPanel-DF1yXzHi.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.12 kB 🔴 +2.81 kB
assets/ExtensionPanel-CzZGprTO.js (removed) 9.19 kB 🟢 -9.19 kB 🟢 -2.51 kB 🟢 -2.23 kB
assets/ExtensionPanel-CKaTqHrI.js (new) 9.19 kB 🔴 +9.19 kB 🔴 +2.51 kB 🔴 +2.23 kB
assets/ServerConfigPanel-CWjzUy2L.js (removed) 6.09 kB 🟢 -6.09 kB 🟢 -1.94 kB 🟢 -1.72 kB
assets/ServerConfigPanel-BUgdtgxN.js (new) 6.09 kB 🔴 +6.09 kB 🔴 +1.94 kB 🔴 +1.72 kB
assets/UserPanel-D9RsiwYd.js (removed) 5.73 kB 🟢 -5.73 kB 🟢 -1.78 kB 🟢 -1.54 kB
assets/UserPanel-FSKz1Zsq.js (new) 5.73 kB 🔴 +5.73 kB 🔴 +1.78 kB 🔴 +1.54 kB
assets/refreshRemoteConfig-CgEoTb_0.js (new) 4.24 kB 🔴 +4.24 kB 🔴 +1.51 kB 🔴 +1.33 kB
assets/refreshRemoteConfig-xzOvvM7x.js (removed) 4.24 kB 🟢 -4.24 kB 🟢 -1.51 kB 🟢 -1.33 kB
assets/remoteConfig-B7jDucs7.js (new) 1.51 kB 🔴 +1.51 kB 🔴 +697 B 🔴 +585 B
assets/remoteConfig-BrojlyaZ.js (removed) 1.51 kB 🟢 -1.51 kB 🟢 -697 B 🟢 -581 B
assets/cloudRemoteConfig-DiIABjOd.js (removed) 951 B 🟢 -951 B 🟢 -516 B 🟢 -417 B
assets/cloudRemoteConfig-RiDUzF8Y.js (new) 951 B 🔴 +951 B 🔴 +512 B 🔴 +418 B
assets/CreditsPanel-Cw1xka0m.js (removed) 116 B 🟢 -116 B 🟢 -95 B 🟢 -91 B
assets/CreditsPanel-DPfauSjd.js (new) 116 B 🔴 +116 B 🔴 +95 B 🔴 +83 B
assets/refreshRemoteConfig-D1yuS4wl.js (removed) 110 B 🟢 -110 B 🟢 -89 B 🟢 -84 B
assets/refreshRemoteConfig-DmOrOe7P.js (new) 110 B 🔴 +110 B 🔴 +89 B 🔴 +92 B

Status: 12 added / 12 removed / 15 unchanged

User & Accounts — 27.5 kB (baseline 27.5 kB) • 🔴 +1 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SignUpForm-2N0-HM-0.js (new) 13.3 kB 🔴 +13.3 kB 🔴 +4.52 kB 🔴 +3.93 kB
assets/SignUpForm-LqMIqENM.js (removed) 13.3 kB 🟢 -13.3 kB 🟢 -4.52 kB 🟢 -3.93 kB
assets/PasswordFields-CfatEeDM.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.46 kB 🔴 +1.29 kB
assets/PasswordFields-DxW_fGtd.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.46 kB 🟢 -1.29 kB
assets/auth-CDTU71WH.js (removed) 3.48 kB 🟢 -3.48 kB 🟢 -1.21 kB 🟢 -1.01 kB
assets/auth-DgRg9DWW.js (new) 3.48 kB 🔴 +3.48 kB 🔴 +1.21 kB 🔴 +1.01 kB
assets/UpdatePasswordContent-BBvg5uet.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -843 B 🟢 -735 B
assets/UpdatePasswordContent-DUjkfOfl.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +838 B 🔴 +732 B
assets/authStore-Bn1mysAA.js (removed) 128 B 🟢 -128 B 🟢 -104 B 🟢 -105 B
assets/authStore-CIzpUHzV.js (new) 128 B 🔴 +128 B 🔴 +104 B 🔴 +106 B
assets/workspaceAuthStore-BrUhbmI9.js (removed) 108 B 🟢 -108 B 🟢 -99 B 🟢 -104 B
assets/workspaceAuthStore-DPE9maR4.js (new) 108 B 🔴 +108 B 🔴 +99 B 🔴 +105 B
assets/auth-CXdTHyMR.js (removed) 105 B 🟢 -105 B 🟢 -96 B 🟢 -80 B
assets/auth-oWjc1rJ9.js (new) 105 B 🔴 +105 B 🔴 +96 B 🔴 +87 B

Status: 7 added / 7 removed / 4 unchanged

Editors & Dialogs — 125 kB (baseline 125 kB) • 🟢 -2 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyHubPublishDialog-BHRmZGmn.js (new) 90.1 kB 🔴 +90.1 kB 🔴 +19.3 kB 🔴 +16.5 kB
assets/ComfyHubPublishDialog-D3jzfrqp.js (removed) 90.1 kB 🟢 -90.1 kB 🟢 -19.3 kB 🟢 -16.5 kB
assets/useShareDialog-zGrCJQeU.js (removed) 23.9 kB 🟢 -23.9 kB 🟢 -5.69 kB 🟢 -5.03 kB
assets/useShareDialog-DihOwa-g.js (new) 23.9 kB 🔴 +23.9 kB 🔴 +5.69 kB 🔴 +5.02 kB
assets/feedbackDialog-C_G4dxTB.js (new) 4.45 kB 🔴 +4.45 kB 🔴 +1.87 kB 🔴 +1.59 kB
assets/feedbackDialog-CsKho1do.js (removed) 4.45 kB 🟢 -4.45 kB 🟢 -1.86 kB 🟢 -1.59 kB
assets/useRangeEditor-CC_S5hLY.js (new) 3.29 kB 🔴 +3.29 kB 🔴 +1.14 kB 🔴 +1.03 kB
assets/useRangeEditor-D3-cP1OM.js (removed) 3.29 kB 🟢 -3.29 kB 🟢 -1.14 kB 🟢 -1.03 kB
assets/SubscriptionInactiveMemberDialog-ahSbojkl.js (new) 2.48 kB 🔴 +2.48 kB 🔴 +997 B 🔴 +851 B
assets/SubscriptionInactiveMemberDialog-x2DfQQ43.js (removed) 2.48 kB 🟢 -2.48 kB 🟢 -997 B 🟢 -852 B
assets/useLayerEditor-C8sTW3Zw.js (removed) 1.01 kB 🟢 -1.01 kB 🟢 -490 B 🟢 -405 B
assets/useLayerEditor-DSZu_coI.js (new) 1.01 kB 🔴 +1.01 kB 🔴 +492 B 🔴 +407 B
assets/ComfyHubPublishDialog-Co-N9-Ud.js (new) 143 B 🔴 +143 B 🔴 +105 B 🔴 +91 B
assets/ComfyHubPublishDialog-CzVVma1g.js (removed) 143 B 🟢 -143 B 🟢 -105 B 🟢 -91 B
assets/useSubscriptionDialog-B0NCniBS.js (new) 108 B 🔴 +108 B 🔴 +102 B 🔴 +90 B
assets/useSubscriptionDialog-DOF3Cq2I.js (removed) 108 B 🟢 -108 B 🟢 -102 B 🟢 -96 B

Status: 8 added / 8 removed

UI Components — 112 kB (baseline 67.1 kB) • 🔴 +44.9 kB

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useMoreOptionsMenu-BXdaIzzs.js (new) 45 kB 🔴 +45 kB 🔴 +10.8 kB 🔴 +9.35 kB
assets/ComfyQueueButton-Cl0_TYJg.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.97 kB 🔴 +3.53 kB
assets/ComfyQueueButton-Czuif1yk.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.97 kB 🟢 -3.52 kB
assets/useTerminalTabs-D_ZiHh6A.js (removed) 11.8 kB 🟢 -11.8 kB 🟢 -3.69 kB 🟢 -3.28 kB
assets/useTerminalTabs-DGO2xqhl.js (new) 11.8 kB 🔴 +11.8 kB 🔴 +3.69 kB 🔴 +3.27 kB
assets/InviteMembersForm-kVBstXAp.js (removed) 8.23 kB 🟢 -8.23 kB 🟢 -2.74 kB 🟢 -2.44 kB
assets/InviteMembersForm-rhEZ5Czl.js (new) 8.23 kB 🔴 +8.23 kB 🔴 +2.74 kB 🔴 +2.43 kB
assets/TopbarBadge-DpmLBLSa.js (removed) 7.65 kB 🟢 -7.65 kB 🟢 -1.83 kB 🟢 -1.62 kB
assets/TopbarBadge-DulYalhI.js (new) 7.65 kB 🔴 +7.65 kB 🔴 +1.83 kB 🔴 +1.62 kB
assets/ScrubableNumberInput-D9K4Tr1y.js (new) 6.49 kB 🔴 +6.49 kB 🔴 +2.17 kB 🔴 +1.92 kB
assets/ScrubableNumberInput-WY0QJxpY.js (removed) 6.49 kB 🟢 -6.49 kB 🟢 -2.17 kB 🟢 -1.92 kB
assets/WidgetInputNumberInput-B4fm5_pE.js (new) 5.83 kB 🔴 +5.83 kB 🔴 +2.07 kB 🔴 +1.84 kB
assets/WidgetInputNumberInput-BoQEK-aT.js (removed) 5.83 kB 🟢 -5.83 kB 🟢 -2.07 kB 🟢 -1.84 kB
assets/Button-CSGiY2kh.js (removed) 4.64 kB 🟢 -4.64 kB 🟢 -1.62 kB 🟢 -1.42 kB
assets/Button-DztiUCa6.js (new) 4.64 kB 🔴 +4.64 kB 🔴 +1.62 kB 🔴 +1.42 kB
assets/SubscribeButton--IGeqYod.js (removed) 2.15 kB 🟢 -2.15 kB 🟢 -968 B 🟢 -848 B
assets/SubscribeButton-BIVpavyK.js (new) 2.15 kB 🔴 +2.15 kB 🔴 +967 B 🔴 +846 B
assets/WidgetButton-CEj8TZJy.js (removed) 1.93 kB 🟢 -1.93 kB 🟢 -886 B 🟢 -776 B
assets/WidgetButton-BOQkSIMB.js (new) 1.91 kB 🔴 +1.91 kB 🔴 +887 B 🔴 +774 B
assets/Input-BMCGAemf.js (removed) 1.76 kB 🟢 -1.76 kB 🟢 -871 B 🟢 -760 B
assets/Input-DX-2vKET.js (new) 1.76 kB 🔴 +1.76 kB 🔴 +870 B 🔴 +765 B
assets/UserAvatar-Be3pfDFa.js (removed) 1.24 kB 🟢 -1.24 kB 🟢 -641 B 🟢 -554 B
assets/UserAvatar-CeRcwZsX.js (new) 1.24 kB 🔴 +1.24 kB 🔴 +639 B 🔴 +554 B
assets/cloudFeedbackTopbarButton-BFAhN2oN.js (removed) 705 B 🟢 -705 B 🟢 -419 B 🟢 -357 B
assets/cloudFeedbackTopbarButton-DZ7JUX4G.js (new) 705 B 🔴 +705 B 🔴 +419 B 🔴 +379 B
assets/ComfyQueueButton-DIXOStuS.js (removed) 128 B 🟢 -128 B 🟢 -99 B 🟢 -89 B
assets/ComfyQueueButton-DqS9R0Xg.js (new) 128 B 🔴 +128 B 🔴 +99 B 🔴 +102 B
assets/toggle-group-BcTHnSVu.js (new) 40 B 🔴 +40 B 🔴 +60 B 🔴 +44 B
assets/toggle-group-D55FZnut.js (removed) 40 B 🟢 -40 B 🟢 -60 B 🟢 -44 B

Status: 15 added / 14 removed

Data & Services — 3.49 MB (baseline 3.53 MB) • 🟢 -34.1 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/settingStore-BNubhuF3.js (removed) 3.16 MB 🟢 -3.16 MB 🟢 -742 kB 🟢 -558 kB
assets/settingStore-DGmzmk6Z.js (new) 3.13 MB 🔴 +3.13 MB 🔴 +734 kB 🔴 +554 kB
assets/api-VWXg-0V-.js (removed) 186 kB 🟢 -186 kB 🟢 -39.8 kB 🟢 -33.4 kB
assets/api-DuHQ-Gt4.js (new) 186 kB 🔴 +186 kB 🔴 +39.8 kB 🔴 +33.3 kB
assets/load3dService-kM4BxrOn.js (removed) 131 kB 🟢 -131 kB 🟢 -29.2 kB 🟢 -24.6 kB
assets/load3dService-C9PXHTXS.js (new) 131 kB 🔴 +131 kB 🔴 +29.2 kB 🔴 +24.6 kB
assets/workflowShareService-CPkalyTd.js (removed) 16.5 kB 🟢 -16.5 kB 🟢 -4.91 kB 🟢 -4.35 kB
assets/workflowShareService-zZd9t6J7.js (new) 16.5 kB 🔴 +16.5 kB 🔴 +4.91 kB 🔴 +4.34 kB
assets/keybindingService-CF91x4aP.js (new) 6.95 kB 🔴 +6.95 kB 🔴 +1.75 kB 🔴 +1.51 kB
assets/keybindingService-l-ypHXD1.js (removed) 6.95 kB 🟢 -6.95 kB 🟢 -1.74 kB 🟢 -1.5 kB
assets/releaseStore-BgKm8NV8.js (removed) 6.72 kB 🟢 -6.72 kB 🟢 -2.03 kB 🟢 -1.77 kB
assets/releaseStore-ufidV6s3.js (new) 6.72 kB 🔴 +6.72 kB 🔴 +2.03 kB 🔴 +1.77 kB
assets/dialogStore-CG1734l2.js (new) 5.35 kB 🔴 +5.35 kB 🔴 +1.64 kB 🔴 +1.43 kB
assets/dialogStore-CTLZ8baA.js (removed) 5.35 kB 🟢 -5.35 kB 🟢 -1.64 kB 🟢 -1.44 kB
assets/extensionStore-CEN1ia57.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.75 kB 🔴 +1.47 kB
assets/systemStatsStore-p5-T3Rxs.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.74 kB 🟢 -1.47 kB
assets/userStore-B5-kgCGd.js (removed) 2.38 kB 🟢 -2.38 kB 🟢 -898 B 🟢 -795 B
assets/userStore-qL62embv.js (new) 2.38 kB 🔴 +2.38 kB 🔴 +897 B 🔴 +796 B
assets/audioService-BjQVVNw4.js (new) 1.71 kB 🔴 +1.71 kB 🔴 +830 B 🔴 +722 B
assets/audioService-Bphu8V3_.js (removed) 1.71 kB 🟢 -1.71 kB 🟢 -828 B 🟢 -731 B
assets/dialogService-2yv8OdXU.js (new) 98 B 🔴 +98 B 🔴 +97 B 🔴 +87 B
assets/dialogService-DPGs2z6H.js (removed) 98 B 🟢 -98 B 🟢 -97 B 🟢 -93 B
assets/releaseStore-BHt4UuBN.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -88 B
assets/releaseStore-DS75N7vl.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +82 B
assets/settingStore-CCxwKiVW.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +81 B
assets/settingStore-mmCz62vi.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -86 B
assets/assetsStore-EHC6CCzv.js (removed) 94 B 🟢 -94 B 🟢 -92 B 🟢 -84 B
assets/assetsStore-LhIPPBHu.js (new) 94 B 🔴 +94 B 🔴 +92 B 🔴 +83 B
assets/api-CpnucEGU.js (new) 62 B 🔴 +62 B 🔴 +74 B 🔴 +66 B
assets/api-RdbSaa9E.js (removed) 62 B 🟢 -62 B 🟢 -74 B 🟢 -66 B

Status: 15 added / 15 removed / 2 unchanged

Utilities & Hooks — 549 kB (baseline 549 kB) • 🟢 -20 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useConflictDetection-CgINeQ5d.js (removed) 236 kB 🟢 -236 kB 🟢 -53.1 kB 🟢 -43.2 kB
assets/useConflictDetection-CwHYj3ba.js (new) 236 kB 🔴 +236 kB 🔴 +53.1 kB 🔴 +43.2 kB
assets/useLayerEditorSession-C4_3VVcY.js (removed) 158 kB 🟢 -158 kB 🟢 -40.8 kB 🟢 -34.3 kB
assets/useLayerEditorSession-Dquwpeal.js (new) 158 kB 🔴 +158 kB 🔴 +40.8 kB 🔴 +34.3 kB
assets/useLoad3d-DOV1dohR.js (removed) 25.8 kB 🟢 -25.8 kB 🟢 -5.8 kB 🟢 -5.14 kB
assets/useLoad3d-BFKVkaB5.js (new) 25.8 kB 🔴 +25.8 kB 🔴 +5.8 kB 🔴 +5.14 kB
assets/useLoad3dViewer-BqVjiIR4.js (new) 21.2 kB 🔴 +21.2 kB 🔴 +4.98 kB 🔴 +4.37 kB
assets/useLoad3dViewer-oWAtuhY_.js (removed) 21.2 kB 🟢 -21.2 kB 🟢 -4.98 kB 🟢 -4.35 kB
assets/useImageQuiet-DHvlu3E-.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -6.12 kB 🟢 -5.28 kB
assets/useImageQuiet-Mi1SqE9H.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +6.12 kB 🔴 +5.27 kB
assets/useImageCrop-Cq1bwapK.js (removed) 14.9 kB 🟢 -14.9 kB 🟢 -3.42 kB 🟢 -2.98 kB
assets/useImageCrop-DxVyEYjH.js (new) 14.9 kB 🔴 +14.9 kB 🔴 +3.42 kB 🔴 +2.98 kB
assets/useDowngradeToPersonal-BPWDrtKm.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -2.74 kB 🟢 -2.35 kB
assets/useDowngradeToPersonal-CDEdJokZ.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +2.74 kB 🔴 +2.35 kB
assets/useFeatureFlags-B2031Xb4.js (new) 7.25 kB 🔴 +7.25 kB 🔴 +2.05 kB 🔴 +1.73 kB
assets/useFeatureFlags-JnSQlaw8.js (removed) 7.25 kB 🟢 -7.25 kB 🟢 -2.05 kB 🟢 -1.74 kB
assets/useTransformState-CZb_AEuX.js (removed) 3.54 kB 🟢 -3.54 kB 🟢 -1.55 kB 🟢 -1.29 kB
assets/useTransformState-pmtqgF1X.js (new) 3.54 kB 🔴 +3.54 kB 🔴 +1.55 kB 🔴 +1.29 kB
assets/useCompositorLayers-B25OMHfh.js (removed) 2.94 kB 🟢 -2.94 kB 🟢 -897 B 🟢 -803 B
assets/useCompositorLayers-DuKoHtxB.js (new) 2.94 kB 🔴 +2.94 kB 🔴 +899 B 🔴 +802 B
assets/useSurveyEligibility-BchrBOs2.js (new) 2.89 kB 🔴 +2.89 kB 🔴 +1.11 kB 🔴 +918 B
assets/useSurveyEligibility-CjhhQ0cC.js (removed) 2.89 kB 🟢 -2.89 kB 🟢 -1.11 kB 🟢 -919 B
assets/useExternalLink-BQ-GVuup.js (new) 2.85 kB 🔴 +2.85 kB 🔴 +1.07 kB 🔴 +922 B
assets/useExternalLink-CVph8Z9F.js (removed) 2.85 kB 🟢 -2.85 kB 🟢 -1.07 kB 🟢 -922 B
assets/assetPreviewUtil-CmT5aayQ.js (removed) 2.35 kB 🟢 -2.35 kB 🟢 -970 B 🟢 -845 B
assets/assetPreviewUtil-Ds1T4UcG.js (new) 2.35 kB 🔴 +2.35 kB 🔴 +969 B 🔴 +846 B
assets/useFeatureUsageTracker-BvTg_wRf.js (new) 2.07 kB 🔴 +2.07 kB 🔴 +910 B 🔴 +772 B
assets/useFeatureUsageTracker-Q0pu2pNa.js (removed) 2.07 kB 🟢 -2.07 kB 🟢 -911 B 🟢 -770 B
assets/downloadUtil-B87WIEEQ.js (new) 2.04 kB 🔴 +2.04 kB 🔴 +906 B 🔴 +727 B
assets/downloadUtil-DWOZsHft.js (removed) 2.04 kB 🟢 -2.04 kB 🟢 -907 B 🟢 -728 B
assets/useTypeformEmbed-CDWSPG2-.js (new) 2.02 kB 🔴 +2.02 kB 🔴 +1.03 kB 🔴 +851 B
assets/useTypeformEmbed-rs5gQo6a.js (removed) 2.02 kB 🟢 -2.02 kB 🟢 -1.03 kB 🟢 -852 B
assets/useUpstreamValue-CCY3euMK.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -757 B 🟢 -684 B
assets/useUpstreamValue-94lq2YmF.js (new) 1.97 kB 🔴 +1.97 kB 🔴 +751 B 🔴 +695 B
assets/useWorkspaceTierLabel-C3U5F3w-.js (removed) 1.93 kB 🟢 -1.93 kB 🟢 -814 B 🟢 -696 B
assets/useWorkspaceTierLabel-CWfemJQM.js (new) 1.93 kB 🔴 +1.93 kB 🔴 +813 B 🔴 +697 B
assets/useErrorHandling-BJrVNl0E.js (new) 1.49 kB 🔴 +1.49 kB 🔴 +613 B 🔴 +523 B
assets/useErrorHandling-DMIQv0dc.js (removed) 1.49 kB 🟢 -1.49 kB 🟢 -614 B 🟢 -528 B
assets/useModalLiftedZIndex-COrVuqHm.js (new) 908 B 🔴 +908 B 🔴 +552 B 🔴 +513 B
assets/useModalLiftedZIndex-cRwoPvNn.js (removed) 908 B 🟢 -908 B 🟢 -553 B 🟢 -511 B
assets/subscriptionCheckoutUtil-BktJhycq.js (removed) 877 B 🟢 -877 B 🟢 -524 B 🟢 -436 B
assets/subscriptionCheckoutUtil-DKVgEXYs.js (new) 877 B 🔴 +877 B 🔴 +523 B 🔴 +437 B
assets/useTextFileContent-CbVf32VM.js (new) 841 B 🔴 +841 B 🔴 +425 B 🔴 +376 B
assets/useTextFileContent-NV8hZfUK.js (removed) 841 B 🟢 -841 B 🟢 -427 B 🟢 -380 B
assets/useSessionCookie-DxfWzAfR.js (new) 652 B 🔴 +652 B 🔴 +336 B 🔴 +293 B
assets/useSessionCookie-Q8vp5_-K.js (removed) 652 B 🟢 -652 B 🟢 -337 B 🟢 -291 B
assets/useLoad3d-BR2wyi6Z.js (removed) 311 B 🟢 -311 B 🟢 -162 B 🟢 -146 B
assets/useLoad3d-CDc9-UFC.js (new) 311 B 🔴 +311 B 🔴 +164 B 🔴 +148 B
assets/useSessionCookie-G3VcJD-A.js (new) 101 B 🔴 +101 B 🔴 +86 B 🔴 +83 B
assets/useSessionCookie-TURPT31U.js (removed) 101 B 🟢 -101 B 🟢 -86 B 🟢 -84 B
assets/useFeatureFlags-BOhj5UAJ.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +81 B
assets/useFeatureFlags-CkLBje4R.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -90 B
assets/useLoad3dViewer-BPGGaYMv.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +86 B
assets/useLoad3dViewer-BTufcB5v.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -89 B
assets/useCurrentUser-CakWb1-o.js (removed) 94 B 🟢 -94 B 🟢 -95 B 🟢 -93 B
assets/useCurrentUser-Cj8UcWuU.js (new) 94 B 🔴 +94 B 🔴 +95 B 🔴 +85 B

Status: 28 added / 28 removed / 9 unchanged

Vendor & Third-Party — 18 MB (baseline 18.1 MB) • 🟢 -55.4 kB

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-other-BbpaYRB2.js (removed) 3.5 MB 🟢 -3.5 MB 🟢 -739 kB 🟢 -566 kB
assets/vendor-other-GEMF1-Ze.js (new) 3.5 MB 🔴 +3.5 MB 🔴 +739 kB 🔴 +566 kB
assets/vendor-primevue-CsKlfmfR.js (new) 1.69 MB 🔴 +1.69 MB 🔴 +305 kB 🔴 +184 kB
assets/vendor-primevue-CTrQSWXs.js (removed) 1.69 MB 🟢 -1.69 MB 🟢 -306 kB 🟢 -184 kB
assets/vendor-yjs-CbNkqTUJ.js (removed) 278 kB 🟢 -278 kB 🟢 -60.9 kB 🟢 -50.7 kB
assets/vendor-yjs-C9OoToaE.js (new) 223 kB 🔴 +223 kB 🔴 +50.3 kB 🔴 +42.2 kB
assets/vendor-vueuse-DOgK3x0v.js (removed) 150 kB 🟢 -150 kB 🟢 -37.7 kB 🟢 -32.3 kB
assets/vendor-vueuse-Cvfp9Fqp.js (new) 150 kB 🔴 +150 kB 🔴 +37.7 kB 🔴 +32.3 kB

Status: 4 added / 4 removed / 14 unchanged

Other — 14.1 MB (baseline 14.1 MB) • 🔴 +231 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/main-Ub70wlYB.js (removed) 323 kB 🟢 -323 kB 🟢 -79.8 kB 🟢 -61.9 kB
assets/main-mvp4Fcxy.js (new) 323 kB 🔴 +323 kB 🔴 +79.8 kB 🔴 +61.9 kB
assets/main-DrCz2UPc.js (removed) 289 kB 🟢 -289 kB 🟢 -72.3 kB 🟢 -57.5 kB
assets/main-DOahGV_5.js (new) 289 kB 🔴 +289 kB 🔴 +72.3 kB 🔴 +57.5 kB
assets/main-DjXGc5-u.js (removed) 274 kB 🟢 -274 kB 🟢 -71.6 kB 🟢 -56.6 kB
assets/main-BmJHQ2dq.js (new) 274 kB 🔴 +274 kB 🔴 +71.6 kB 🔴 +56.6 kB
assets/main-D2VI9Zum.js (removed) 265 kB 🟢 -265 kB 🟢 -70.9 kB 🟢 -55.7 kB
assets/main-DiyQRTxr.js (new) 265 kB 🔴 +265 kB 🔴 +70.9 kB 🔴 +55.7 kB
assets/main-D0DYIY6J.js (removed) 256 kB 🟢 -256 kB 🟢 -68.6 kB 🟢 -54.9 kB
assets/main-C1oUVDwn.js (new) 256 kB 🔴 +256 kB 🔴 +68.6 kB 🔴 +54.9 kB
assets/main-C4mQZFtQ.js (removed) 241 kB 🟢 -241 kB 🟢 -69.5 kB 🟢 -57.4 kB
assets/main-ax_Fye00.js (new) 241 kB 🔴 +241 kB 🔴 +69.5 kB 🔴 +57.4 kB
assets/main-DCNYc_Q8.js (removed) 239 kB 🟢 -239 kB 🟢 -68.4 kB 🟢 -54.6 kB
assets/main-DJKmcZhT.js (new) 239 kB 🔴 +239 kB 🔴 +68.4 kB 🔴 +54.6 kB
assets/main-EfuGA8is.js (removed) 233 kB 🟢 -233 kB 🟢 -67.7 kB 🟢 -55.8 kB
assets/main-DUl6BmyN.js (new) 233 kB 🔴 +233 kB 🔴 +67.7 kB 🔴 +55.8 kB
assets/main-QXmcpGfd.js (removed) 230 kB 🟢 -230 kB 🟢 -67.9 kB 🟢 -56.3 kB
assets/main-DaJ2OtCD.js (new) 230 kB 🔴 +230 kB 🔴 +67.9 kB 🔴 +56.3 kB
assets/main-jwnbHe2t.js (removed) 228 kB 🟢 -228 kB 🟢 -66.8 kB 🟢 -55.6 kB
assets/main-BFm7u-g9.js (new) 228 kB 🔴 +228 kB 🔴 +66.8 kB 🔴 +55.6 kB
assets/main-Db7znwvM.js (new) 207 kB 🔴 +207 kB 🔴 +60.2 kB 🔴 +50.5 kB
assets/main-B1PcX3Rv.js (removed) 207 kB 🟢 -207 kB 🟢 -60.2 kB 🟢 -50.5 kB
assets/main-B3N4wc59.js (removed) 203 kB 🟢 -203 kB 🟢 -66.7 kB 🟢 -52.7 kB
assets/main-BLleYs-6.js (new) 203 kB 🔴 +203 kB 🔴 +66.7 kB 🔴 +52.8 kB
assets/main-pHRK9r-1.js (removed) 200 kB 🟢 -200 kB 🟢 -66.3 kB 🟢 -52.4 kB
assets/main-DoXUKKzS.js (new) 200 kB 🔴 +200 kB 🔴 +66.3 kB 🔴 +52.4 kB
assets/core-DVuvQFyB.js (removed) 115 kB 🟢 -115 kB 🟢 -30 kB 🟢 -25.4 kB
assets/core-C6uKnulv.js (new) 115 kB 🔴 +115 kB 🔴 +30.1 kB 🔴 +25.5 kB
assets/WorkspaceSettingsPanelContent-B-w_-1jN.js (removed) 109 kB 🟢 -109 kB 🟢 -21.4 kB 🟢 -18 kB
assets/WorkspaceSettingsPanelContent-D_A81jHp.js (new) 109 kB 🔴 +109 kB 🔴 +21.4 kB 🔴 +18 kB
assets/WidgetSelect-Db8GRYX3.js (removed) 88.8 kB 🟢 -88.8 kB 🟢 -20.1 kB 🟢 -17.3 kB
assets/WidgetSelect-smj7Nb0j.js (new) 88.7 kB 🔴 +88.7 kB 🔴 +20.1 kB 🔴 +17.2 kB
assets/Load3D-Bf1xmqrr.js (new) 73.3 kB 🔴 +73.3 kB 🔴 +12.1 kB 🔴 +10.3 kB
assets/Load3D-DsHcwe1u.js (removed) 73.3 kB 🟢 -73.3 kB 🟢 -12.1 kB 🟢 -10.3 kB
assets/WidgetVideoEdit-ach99UbM.js (new) 71.2 kB 🔴 +71.2 kB 🔴 +16.9 kB 🔴 +14.9 kB
assets/WidgetVideoEdit-CLmFvV29.js (removed) 71.2 kB 🟢 -71.2 kB 🟢 -16.9 kB 🟢 -14.9 kB
assets/SubscriptionTransitionPreviewWorkspace-CbQamyX7.js (removed) 66.4 kB 🟢 -66.4 kB 🟢 -13.4 kB 🟢 -11.7 kB
assets/SubscriptionTransitionPreviewWorkspace-CN5F4umH.js (new) 66.4 kB 🔴 +66.4 kB 🔴 +13.4 kB 🔴 +11.7 kB
assets/Preview3d-B-EQOrEV.js (removed) 50.9 kB 🟢 -50.9 kB 🟢 -8.32 kB 🟢 -7.25 kB
assets/Preview3d-DvZBTO8-.js (new) 50.9 kB 🔴 +50.9 kB 🔴 +8.32 kB 🔴 +7.25 kB
assets/main-Bc7irCPU.js (new) 47.4 kB 🔴 +47.4 kB 🔴 +13.7 kB 🔴 +11.9 kB
assets/main-DH-9KxPh.js (removed) 47.4 kB 🟢 -47.4 kB 🟢 -13.7 kB 🟢 -11.9 kB
assets/SubscriptionRequiredDialogContentUnified-Be-MPqXJ.js (removed) 42.7 kB 🟢 -42.7 kB 🟢 -9.38 kB 🟢 -8.18 kB
assets/SubscriptionRequiredDialogContentUnified-BfwO8IGC.js (new) 42.7 kB 🔴 +42.7 kB 🔴 +9.38 kB 🔴 +8.18 kB
assets/LayerEditorContent-Bqdnb3rZ.js (new) 42.5 kB 🔴 +42.5 kB 🔴 +9.62 kB 🔴 +8.43 kB
assets/LayerEditorContent-DrzYYt5-.js (removed) 42.5 kB 🟢 -42.5 kB 🟢 -9.62 kB 🟢 -8.41 kB
assets/WidgetBoundingBoxes-CdpEI2Et.js (new) 33.7 kB 🔴 +33.7 kB 🔴 +9.16 kB 🔴 +8.12 kB
assets/WidgetBoundingBoxes-pmFs-G0T.js (removed) 33.7 kB 🟢 -33.7 kB 🟢 -9.16 kB 🟢 -8.12 kB
assets/WidgetPainter-CXIhsj8_.js (new) 32.6 kB 🔴 +32.6 kB 🔴 +7.88 kB 🔴 +6.96 kB
assets/WidgetPainter-D8uN7vd4.js (removed) 32.6 kB 🟢 -32.6 kB 🟢 -7.88 kB 🟢 -6.97 kB
assets/Load3dViewerContent-CAaIUjRq.js (new) 30.8 kB 🔴 +30.8 kB 🔴 +6.28 kB 🔴 +5.44 kB
assets/Load3dViewerContent-D8lCgSL2.js (removed) 30.8 kB 🟢 -30.8 kB 🟢 -6.28 kB 🟢 -5.45 kB
assets/HdrViewerContent-BXBoFm5u.js (new) 29.8 kB 🔴 +29.8 kB 🔴 +8.25 kB 🔴 +7.27 kB
assets/HdrViewerContent-DOeo4-7t.js (removed) 29.8 kB 🟢 -29.8 kB 🟢 -8.25 kB 🟢 -7.28 kB
assets/ColorPicker-BAKHGd7b.js (removed) 29.2 kB 🟢 -29.2 kB 🟢 -7.58 kB 🟢 -6.68 kB
assets/ColorPicker-BlopWXCL.js (new) 29.2 kB 🔴 +29.2 kB 🔴 +7.59 kB 🔴 +6.67 kB
assets/SubscriptionRequiredDialogContent-C1TCDlxa.js (removed) 26.9 kB 🟢 -26.9 kB 🟢 -6.36 kB 🟢 -5.6 kB
assets/SubscriptionRequiredDialogContent-C9ZgN_fZ.js (new) 26.9 kB 🔴 +26.9 kB 🔴 +6.35 kB 🔴 +5.6 kB
assets/SubscriptionRequiredDialogContentWorkspace-C1R8PyNO.js (removed) 25.1 kB 🟢 -25.1 kB 🟢 -5.81 kB 🟢 -5.11 kB
assets/SubscriptionRequiredDialogContentWorkspace-Co0Mh87_.js (new) 25.1 kB 🔴 +25.1 kB 🔴 +5.8 kB 🔴 +5.11 kB
assets/initHostTelemetry-B4XsZ6_L.js (new) 24.5 kB 🔴 +24.5 kB 🔴 +5.82 kB 🔴 +4.83 kB
assets/initHostTelemetry-CzPdqzep.js (removed) 24.5 kB 🟢 -24.5 kB 🟢 -5.82 kB 🟢 -4.82 kB
assets/load3d-n-ICrO4v.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.41 kB 🔴 +4.68 kB
assets/load3d-Ci3a0c2v.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.4 kB 🟢 -4.67 kB
assets/SignInContent-nvFDgZnX.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5.17 kB 🔴 +4.53 kB
assets/SignInContent-BZOmg8QW.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -5.18 kB 🟢 -4.53 kB
assets/DialogHeader-BB-NzYAS.js (new) 17.5 kB 🔴 +17.5 kB 🔴 +5.27 kB 🔴 +4.61 kB
assets/DialogHeader-DNCZSY4g.js (removed) 17.5 kB 🟢 -17.5 kB 🟢 -5.27 kB 🟢 -4.6 kB
assets/i18n-B-cy84Fi.js (new) 16.7 kB 🔴 +16.7 kB 🔴 +4.55 kB 🔴 +3.81 kB
assets/i18n-BmS4EN6x.js (removed) 16.7 kB 🟢 -16.7 kB 🟢 -4.55 kB 🟢 -3.82 kB
assets/WidgetRecordAudio-Ds8LTv93.js (removed) 16.6 kB 🟢 -16.6 kB 🟢 -4.6 kB 🟢 -4.11 kB
assets/WidgetRecordAudio-PIOTf2-q.js (new) 16.6 kB 🔴 +16.6 kB 🔴 +4.6 kB 🔴 +4.09 kB
assets/CurrentUserPopoverWorkspace-CPW0Q_kW.js (removed) 15.9 kB 🟢 -15.9 kB 🟢 -3.86 kB 🟢 -3.4 kB
assets/CurrentUserPopoverWorkspace-CuVcQ63O.js (new) 15.9 kB 🔴 +15.9 kB 🔴 +3.86 kB 🔴 +3.4 kB
assets/WidgetInputNumber-CfSWBDyc.js (removed) 13.9 kB 🟢 -13.9 kB 🟢 -3.63 kB 🟢 -3.22 kB
assets/WidgetInputNumber-fvam4aIk.js (new) 13.9 kB 🔴 +13.9 kB 🔴 +3.63 kB 🔴 +3.21 kB
assets/WidgetRange-D74c42mW.js (removed) 13.7 kB 🟢 -13.7 kB 🟢 -3.55 kB 🟢 -3.12 kB
assets/WidgetRange-VHsXFVxN.js (new) 13.7 kB 🔴 +13.7 kB 🔴 +3.55 kB 🔴 +3.12 kB
assets/WaveAudioPlayer-Cl5q9-Rs.js (removed) 12.8 kB 🟢 -12.8 kB 🟢 -3.46 kB 🟢 -3.05 kB
assets/WaveAudioPlayer-Djgm7AAw.js (new) 12.8 kB 🔴 +12.8 kB 🔴 +3.46 kB 🔴 +3.03 kB
assets/WidgetCurve-B91Z7VfC.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.47 kB 🟢 -3.14 kB
assets/WidgetCurve-wVPeWBNL.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.48 kB 🔴 +3.14 kB
assets/SelectValue-DGhjQzA5.js (new) 10.6 kB 🔴 +10.6 kB 🔴 +2.61 kB 🔴 +2.33 kB
assets/SelectValue-sIEkyo5B.js (removed) 10.6 kB 🟢 -10.6 kB 🟢 -2.61 kB 🟢 -2.33 kB
assets/AudioPreviewPlayer-9iT1_2lU.js (new) 10.6 kB 🔴 +10.6 kB 🔴 +3.04 kB 🔴 +2.72 kB
assets/AudioPreviewPlayer-DqsX1YMI.js (removed) 10.6 kB 🟢 -10.6 kB 🟢 -3.05 kB 🟢 -2.72 kB
assets/TeamWorkspacesDialogContent-D-DhdXfY.js (removed) 10.3 kB 🟢 -10.3 kB 🟢 -2.97 kB 🟢 -2.62 kB
assets/TeamWorkspacesDialogContent-tqQBCaC9.js (new) 10.3 kB 🔴 +10.3 kB 🔴 +2.97 kB 🔴 +2.63 kB
assets/onboardingCloudRoutes-DWhcqySt.js (removed) 9.49 kB 🟢 -9.49 kB 🟢 -2.85 kB 🟢 -2.44 kB
assets/onboardingCloudRoutes-BoX0JoS_.js (new) 9.49 kB 🔴 +9.49 kB 🔴 +2.84 kB 🔴 +2.44 kB
assets/Load3DConfiguration-CdipRi4W.js (removed) 8.91 kB 🟢 -8.91 kB 🟢 -2.61 kB 🟢 -2.3 kB
assets/Load3DConfiguration-DbL7TLtP.js (new) 8.91 kB 🔴 +8.91 kB 🔴 +2.61 kB 🔴 +2.3 kB
assets/WidgetImageCrop-BedLQxU7.js (new) 8.49 kB 🔴 +8.49 kB 🔴 +2.66 kB 🔴 +2.34 kB
assets/WidgetImageCrop-Bm3ZpdLy.js (removed) 8.49 kB 🟢 -8.49 kB 🟢 -2.65 kB 🟢 -2.34 kB
assets/SetMemberCreditLimitDialogContent-CHkC7L4e.js (new) 8.47 kB 🔴 +8.47 kB 🔴 +2.34 kB 🔴 +2.05 kB
assets/SetMemberCreditLimitDialogContent-DYLN47KF.js (removed) 8.47 kB 🟢 -8.47 kB 🟢 -2.34 kB 🟢 -2.05 kB
assets/nodeTemplates-dQONQRl_.js (new) 8.32 kB 🔴 +8.32 kB 🔴 +2.85 kB 🔴 +2.51 kB
assets/nodeTemplates-P1lI8yxY.js (removed) 8.32 kB 🟢 -8.32 kB 🟢 -2.85 kB 🟢 -2.5 kB
assets/WidgetImageCompare-BE4LdHR2.js (new) 8.13 kB 🔴 +8.13 kB 🔴 +2.3 kB 🔴 +2.02 kB
assets/WidgetImageCompare-D4Ks4VkP.js (removed) 8.13 kB 🟢 -8.13 kB 🟢 -2.3 kB 🟢 -2.02 kB
assets/Popover--fUHJfN3.js (new) 8.13 kB 🔴 +8.13 kB 🔴 +2.88 kB 🔴 +2.55 kB
assets/WorkspaceSwitcherPopover-B-pbY35s.js (removed) 8.01 kB 🟢 -8.01 kB 🟢 -2.38 kB 🟢 -2.12 kB
assets/WorkspaceSwitcherPopover-CF-LXV58.js (new) 8.01 kB 🔴 +8.01 kB 🔴 +2.38 kB 🔴 +2.12 kB
assets/NightlySurveyController-ClAkM9GJ.js (removed) 7.5 kB 🟢 -7.5 kB 🟢 -2.55 kB 🟢 -2.24 kB
assets/NightlySurveyController-i6-N-bEL.js (new) 7.5 kB 🔴 +7.5 kB 🔴 +2.55 kB 🔴 +2.25 kB
assets/Popover-CGyZcZ1L.js (removed) 7.48 kB 🟢 -7.48 kB 🟢 -2.68 kB 🟢 -2.36 kB
assets/CloudRunButtonWrapper-8odV5uXZ.js (new) 7.29 kB 🔴 +7.29 kB 🔴 +2.3 kB 🔴 +2.01 kB
assets/CloudRunButtonWrapper-Cs3KGUGm.js (removed) 7.29 kB 🟢 -7.29 kB 🟢 -2.29 kB 🟢 -2.01 kB
assets/WidgetWithControl-BDtFM6IC.js (removed) 6.51 kB 🟢 -6.51 kB 🟢 -2.66 kB 🟢 -2.33 kB
assets/WidgetWithControl-D97PNbr6.js (new) 6.5 kB 🔴 +6.5 kB 🔴 +2.65 kB 🔴 +2.35 kB
assets/missingModelMetadata-7TtRXhA5.js (new) 6.45 kB 🔴 +6.45 kB 🔴 +2.21 kB 🔴 +1.93 kB
assets/missingModelMetadata-BdEn50RX.js (removed) 6.45 kB 🟢 -6.45 kB 🟢 -2.2 kB 🟢 -1.93 kB
assets/AnimationControls-CIzQpa0V.js (new) 6.1 kB 🔴 +6.1 kB 🔴 +1.86 kB 🔴 +1.63 kB
assets/AnimationControls-TVbncURH.js (removed) 6.1 kB 🟢 -6.1 kB 🟢 -1.85 kB 🟢 -1.64 kB
assets/CancelSubscriptionDialogContent-3f5MLIdu.js (new) 5.97 kB 🔴 +5.97 kB 🔴 +1.98 kB 🔴 +1.74 kB
assets/CancelSubscriptionDialogContent-DwCmnKXT.js (removed) 5.97 kB 🟢 -5.97 kB 🟢 -1.98 kB 🟢 -1.75 kB
assets/main-3qdiys3A.js (new) 5.94 kB 🔴 +5.94 kB 🔴 +1.92 kB 🔴 +1.59 kB
assets/main-CDFDBvyu.js (removed) 5.92 kB 🟢 -5.92 kB 🟢 -1.91 kB 🟢 -1.6 kB
assets/load3dPreviewExtensions-B3E_JfJl.js (new) 5.88 kB 🔴 +5.88 kB 🔴 +1.81 kB 🔴 +1.6 kB
assets/load3dPreviewExtensions-DSzcKbeE.js (removed) 5.88 kB 🟢 -5.88 kB 🟢 -1.81 kB 🟢 -1.6 kB
assets/src-BhTzA98X.js (new) 5.69 kB 🔴 +5.69 kB 🔴 +1.61 kB 🔴 +1.47 kB
assets/src-CxE1thV8.js (removed) 5.69 kB 🟢 -5.69 kB 🟢 -1.61 kB 🟢 -1.46 kB
assets/DowngradeRemoveMembersDialogContent-CVwieKyg.js (removed) 5.58 kB 🟢 -5.58 kB 🟢 -1.89 kB 🟢 -1.64 kB
assets/DowngradeRemoveMembersDialogContent-sH-FloLj.js (new) 5.58 kB 🔴 +5.58 kB 🔴 +1.89 kB 🔴 +1.65 kB
assets/launchCancellationFlow-Ce1X9lFr.js (removed) 5.18 kB 🟢 -5.18 kB 🟢 -1.78 kB 🟢 -1.56 kB
assets/launchCancellationFlow-CT9gIUGp.js (new) 5.18 kB 🔴 +5.18 kB 🔴 +1.78 kB 🔴 +1.56 kB
assets/CreateWorkspaceDialogContent-Bo1n2VHk.js (new) 5.12 kB 🔴 +5.12 kB 🔴 +1.79 kB 🔴 +1.55 kB
assets/CreateWorkspaceDialogContent-xajE30_E.js (removed) 5.12 kB 🟢 -5.12 kB 🟢 -1.79 kB 🟢 -1.55 kB
assets/CloudNotificationContent-Cwac8uOz.js (new) 5.05 kB 🔴 +5.05 kB 🔴 +1.76 kB 🔴 +1.51 kB
assets/CloudNotificationContent-uj-bNvkE.js (removed) 5.05 kB 🟢 -5.05 kB 🟢 -1.76 kB 🟢 -1.51 kB
assets/ChangeMemberRoleDialogContent-Ci9kVejv.js (new) 4.97 kB 🔴 +4.97 kB 🔴 +1.63 kB 🔴 +1.43 kB
assets/ChangeMemberRoleDialogContent-J5x7C_0L.js (removed) 4.97 kB 🟢 -4.97 kB 🟢 -1.64 kB 🟢 -1.43 kB
assets/InviteMemberDialogContent-DtsTFXhA.js (removed) 4.96 kB 🟢 -4.96 kB 🟢 -1.64 kB 🟢 -1.44 kB
assets/InviteMemberDialogContent-tUONVIhf.js (new) 4.96 kB 🔴 +4.96 kB 🔴 +1.64 kB 🔴 +1.44 kB
assets/EditWorkspaceDialogContent-B28qzV2P.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.76 kB 🟢 -1.52 kB
assets/EditWorkspaceDialogContent-BVskgXzn.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.76 kB 🔴 +1.52 kB
assets/PaletteSwatchRow-DtREg7YR.js (new) 4.86 kB 🔴 +4.86 kB 🔴 +1.87 kB 🔴 +1.66 kB
assets/PaletteSwatchRow-DWp-KLID.js (removed) 4.86 kB 🟢 -4.86 kB 🟢 -1.87 kB 🟢 -1.67 kB
assets/WidgetTextarea-DeIH5LKL.js (new) 4.83 kB 🔴 +4.83 kB 🔴 +1.88 kB 🔴 +1.64 kB
assets/WidgetTextarea-CNhA-DMQ.js (removed) 4.83 kB 🟢 -4.83 kB 🟢 -1.88 kB 🟢 -1.64 kB
assets/saveMesh-Cgr3BPbD.js (new) 4.76 kB 🔴 +4.76 kB 🔴 +1.52 kB 🔴 +1.34 kB
assets/saveMesh-DcskMVjl.js (removed) 4.76 kB 🟢 -4.76 kB 🟢 -1.51 kB 🟢 -1.34 kB
assets/ValueControlPopover-9oeoH2Vs.js (new) 4.49 kB 🔴 +4.49 kB 🔴 +1.55 kB 🔴 +1.38 kB
assets/ValueControlPopover-Bj2y2p3p.js (removed) 4.49 kB 🟢 -4.49 kB 🟢 -1.55 kB 🟢 -1.38 kB
assets/ToggleGroupItem-Db6Mc4z5.js (removed) 4.14 kB 🟢 -4.14 kB 🟢 -1.41 kB 🟢 -1.24 kB
assets/ToggleGroupItem-Dp9aeJnz.js (new) 4.14 kB 🔴 +4.14 kB 🔴 +1.41 kB 🔴 +1.24 kB
assets/ApiNodesSignInContent-CRraK5Ql.js (new) 4.08 kB 🔴 +4.08 kB 🔴 +1.31 kB 🔴 +1.15 kB
assets/ApiNodesSignInContent-DqAs7xpi.js (removed) 4.08 kB 🟢 -4.08 kB 🟢 -1.31 kB 🟢 -1.15 kB
assets/Slider-BDxYwWqn.js (new) 3.91 kB 🔴 +3.91 kB 🔴 +1.43 kB 🔴 +1.25 kB
assets/Slider-CAAJfsIS.js (removed) 3.91 kB 🟢 -3.91 kB 🟢 -1.43 kB 🟢 -1.25 kB
assets/DeleteWorkspaceDialogContent-BOiOuxF1.js (new) 3.84 kB 🔴 +3.84 kB 🔴 +1.43 kB 🔴 +1.24 kB
assets/DeleteWorkspaceDialogContent-wgBe6KGl.js (removed) 3.84 kB 🟢 -3.84 kB 🟢 -1.44 kB 🟢 -1.24 kB
assets/WidgetBoundingBox-CilszWJR.js (removed) 3.76 kB 🟢 -3.76 kB 🟢 -1.03 kB 🟢 -894 B
assets/WidgetBoundingBox-DAKW0eBB.js (new) 3.76 kB 🔴 +3.76 kB 🔴 +1.03 kB 🔴 +899 B
assets/RemoveMemberDialogContent-BVg2Ywvb.js (new) 3.76 kB 🔴 +3.76 kB 🔴 +1.38 kB 🔴 +1.19 kB
assets/RemoveMemberDialogContent-CmWlOtuh.js (removed) 3.76 kB 🟢 -3.76 kB 🟢 -1.38 kB 🟢 -1.23 kB
assets/WidgetGalleria-B33HqfsC.js (new) 3.75 kB 🔴 +3.75 kB 🔴 +1.45 kB 🔴 +1.29 kB
assets/WidgetGalleria-BCG27W3j.js (removed) 3.75 kB 🟢 -3.75 kB 🟢 -1.45 kB 🟢 -1.29 kB
assets/RevokeInviteDialogContent-Czd7MLxb.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.39 kB 🟢 -1.21 kB
assets/RevokeInviteDialogContent-yqgd247v.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.39 kB 🔴 +1.21 kB
assets/LeaveWorkspaceDialogContent-BAZq_uf-.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.38 kB 🔴 +1.21 kB
assets/LeaveWorkspaceDialogContent-CerOYOa5.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.39 kB 🟢 -1.19 kB
assets/WidgetToggleSwitch-Cf9GGhbp.js (new) 3.56 kB 🔴 +3.56 kB 🔴 +1.3 kB 🔴 +1.14 kB
assets/WidgetToggleSwitch-CnqFWJ6R.js (removed) 3.56 kB 🟢 -3.56 kB 🟢 -1.3 kB 🟢 -1.14 kB
assets/InviteMemberUpsellDialogContent-CnXP7b8G.js (removed) 3.47 kB 🟢 -3.47 kB 🟢 -1.24 kB 🟢 -1.09 kB
assets/InviteMemberUpsellDialogContent-DHTHsL0z.js (new) 3.47 kB 🔴 +3.47 kB 🔴 +1.24 kB 🔴 +1.08 kB
assets/workspaceCheckoutTelemetry-8RNtuc4c.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.52 kB 🔴 +1.32 kB
assets/workspaceCheckoutTelemetry-Dh-whnxd.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.52 kB 🟢 -1.32 kB
assets/GlobalToast-BybNsPpa.js (removed) 3.25 kB 🟢 -3.25 kB 🟢 -1.3 kB 🟢 -1.11 kB
assets/GlobalToast-CWmx8I3I.js (new) 3.25 kB 🔴 +3.25 kB 🔴 +1.3 kB 🔴 +1.11 kB
assets/Media3DTop-BML03bTN.js (removed) 3.21 kB 🟢 -3.21 kB 🟢 -1.26 kB 🟢 -1.1 kB
assets/Media3DTop-XDtblXe7.js (new) 3.21 kB 🔴 +3.21 kB 🔴 +1.26 kB 🔴 +1.11 kB
assets/WidgetMarkdown-BiVHwHpy.js (removed) 3.07 kB 🟢 -3.07 kB 🟢 -1.26 kB 🟢 -1.11 kB
assets/WidgetMarkdown-CaF-HVAU.js (new) 3.07 kB 🔴 +3.07 kB 🔴 +1.26 kB 🔴 +1.11 kB
assets/WidgetInputText-BLoSIqfT.js (new) 3.04 kB 🔴 +3.04 kB 🔴 +1.27 kB 🔴 +1.14 kB
assets/WidgetInputText-Cl1CGXcE.js (removed) 3.04 kB 🟢 -3.04 kB 🟢 -1.27 kB 🟢 -1.14 kB
assets/MediaVideoTop-7GGGEnch.js (removed) 2.96 kB 🟢 -2.96 kB 🟢 -1.19 kB 🟢 -1.04 kB
assets/MediaVideoTop-Cgsbovvs.js (new) 2.96 kB 🔴 +2.96 kB 🔴 +1.19 kB 🔴 +1.05 kB
assets/load3dAdvanced-CGomYPG6.js (removed) 2.82 kB 🟢 -2.82 kB 🟢 -1.09 kB 🟢 -954 B
assets/load3dAdvanced-ze9hgCwu.js (new) 2.82 kB 🔴 +2.82 kB 🔴 +1.1 kB 🔴 +955 B
assets/ErrorPanelSurveyCta-CMF77lpP.js (new) 2.64 kB 🔴 +2.64 kB 🔴 +1.14 kB 🔴 +1.01 kB
assets/ErrorPanelSurveyCta-DAVZNOF1.js (removed) 2.64 kB 🟢 -2.64 kB 🟢 -1.14 kB 🟢 -1.01 kB
assets/Switch-BoP_AkwH.js (new) 2.49 kB 🔴 +2.49 kB 🔴 +1.09 kB 🔴 +946 B
assets/Switch-Xs1nDhSC.js (removed) 2.49 kB 🟢 -2.49 kB 🟢 -1.09 kB 🟢 -942 B
assets/SubscribeToRun-D586E5M8.js (new) 2.39 kB 🔴 +2.39 kB 🔴 +1.03 kB 🔴 +907 B
assets/SubscribeToRun-sqYunfc9.js (removed) 2.39 kB 🟢 -2.39 kB 🟢 -1.03 kB 🟢 -907 B
assets/WidgetLayoutField-BLL_cIeg.js (new) 2.38 kB 🔴 +2.38 kB 🔴 +1.02 kB 🔴 +930 B
assets/WidgetLayoutField-twvzBpEE.js (removed) 2.38 kB 🟢 -2.38 kB 🟢 -1.02 kB 🟢 -933 B
assets/WidgetChart-Cbm6_FLG.js (new) 2.36 kB 🔴 +2.36 kB 🔴 +999 B 🔴 +864 B
assets/WidgetChart-DhIozH2z.js (removed) 2.36 kB 🟢 -2.36 kB 🟢 -1 kB 🟢 -864 B
assets/TopBarHeader-BhWyosuT.js (removed) 2.01 kB 🟢 -2.01 kB 🟢 -842 B 🟢 -729 B
assets/TopBarHeader-Cvk4j0hv.js (new) 2.01 kB 🔴 +2.01 kB 🔴 +841 B 🔴 +733 B
assets/MediaImageTop-CAOzoh__.js (new) 1.95 kB 🔴 +1.95 kB 🔴 +928 B 🔴 +797 B
assets/MediaImageTop-ZAb3nXP7.js (removed) 1.95 kB 🟢 -1.95 kB 🟢 -925 B 🟢 -794 B
assets/TypeformEmbed-ClNj9B2A.js (removed) 1.83 kB 🟢 -1.83 kB 🟢 -810 B 🟢 -709 B
assets/TypeformEmbed-CzmaTPG-.js (new) 1.83 kB 🔴 +1.83 kB 🔴 +811 B 🔴 +711 B
assets/MediaTextTop-BDofYScQ.js (removed) 1.77 kB 🟢 -1.77 kB 🟢 -855 B 🟢 -728 B
assets/MediaTextTop-Qo4dru8r.js (new) 1.77 kB 🔴 +1.77 kB 🔴 +853 B 🔴 +728 B
assets/WidgetColorPicker-D1LYfWS8.js (new) 1.69 kB 🔴 +1.69 kB 🔴 +754 B 🔴 +672 B
assets/WidgetColorPicker-LzjWdNOc.js (removed) 1.69 kB 🟢 -1.69 kB 🟢 -757 B 🟢 -674 B
assets/Loader-BjGe9sS6.js (removed) 1.64 kB 🟢 -1.64 kB 🟢 -782 B 🟢 -691 B
assets/Loader-Ch4mpgy6.js (new) 1.64 kB 🔴 +1.64 kB 🔴 +782 B 🔴 +694 B
assets/WidgetColors-BErteo74.js (new) 1.63 kB 🔴 +1.63 kB 🔴 +805 B 🔴 +709 B
assets/WidgetColors-CaHWfhCd.js (removed) 1.63 kB 🟢 -1.63 kB 🟢 -806 B 🟢 -709 B
assets/MediaAudioTop-7sm2P6SH.js (removed) 1.62 kB 🟢 -1.62 kB 🟢 -808 B 🟢 -668 B
assets/MediaAudioTop-BmD1hmW1.js (new) 1.62 kB 🔴 +1.62 kB 🔴 +808 B 🔴 +670 B
assets/Textarea-BAbA82lg.js (new) 1.56 kB 🔴 +1.56 kB 🔴 +810 B 🔴 +713 B
assets/Textarea-DyP8nI1x.js (removed) 1.56 kB 🟢 -1.56 kB 🟢 -810 B 🟢 -713 B
assets/signInSchema-DG0LGeke.js (new) 1.55 kB 🔴 +1.55 kB 🔴 +553 B 🔴 +485 B
assets/signInSchema-DyGOMsLX.js (removed) 1.55 kB 🟢 -1.55 kB 🟢 -555 B 🟢 -520 B
assets/VideoPlayOverlay-CzfiEpxR.js (removed) 1.46 kB 🟢 -1.46 kB 🟢 -729 B 🟢 -645 B
assets/VideoPlayOverlay-zEDFCRpo.js (new) 1.46 kB 🔴 +1.46 kB 🔴 +729 B 🔴 +645 B
assets/ButtonGroup-Bru4sDIC.js (removed) 1.24 kB 🟢 -1.24 kB 🟢 -643 B 🟢 -559 B
assets/ButtonGroup-Sfsz6VKD.js (new) 1.24 kB 🔴 +1.24 kB 🔴 +642 B 🔴 +561 B
assets/DialogClose-Cb_87tO6.js (removed) 1.18 kB 🟢 -1.18 kB 🟢 -610 B 🟢 -546 B
assets/DialogClose-D1YJfB0j.js (new) 1.18 kB 🔴 +1.18 kB 🔴 +609 B 🔴 +545 B
assets/cloudSessionCookie-B2lPbofa.js (removed) 933 B 🟢 -933 B 🟢 -434 B 🟢 -378 B
assets/cloudSessionCookie-BssCGNUt.js (new) 933 B 🔴 +933 B 🔴 +432 B 🔴 +376 B
assets/cloudBadges-BMPSByup.js (removed) 922 B 🟢 -922 B 🟢 -512 B 🟢 -441 B
assets/cloudBadges-XmXYq8D1.js (new) 922 B 🔴 +922 B 🔴 +515 B 🔴 +439 B
assets/Load3DAdvanced-B6DMUXiQ.js (new) 761 B 🔴 +761 B 🔴 +422 B 🔴 +357 B
assets/Load3DAdvanced-CVQhOTch.js (removed) 761 B 🟢 -761 B 🟢 -422 B 🟢 -358 B
assets/vRekaZIndex-DE5prqA2.js (removed) 505 B 🟢 -505 B 🟢 -331 B 🟢 -273 B
assets/vRekaZIndex-DZQTlbbi.js (new) 505 B 🔴 +505 B 🔴 +330 B 🔴 +280 B
assets/nightlyBadges-Bjd-Tw42.js (removed) 411 B 🟢 -411 B 🟢 -271 B 🟢 -231 B
assets/nightlyBadges-Cvk7greW.js (new) 411 B 🔴 +411 B 🔴 +272 B 🔴 +231 B
assets/src-CjrdRgC5.js (new) 407 B 🔴 +407 B 🔴 +287 B 🔴 +246 B
assets/src-D6Hzdwct.js (removed) 407 B 🟢 -407 B 🟢 -289 B 🟢 -244 B
assets/Load3dViewerContent-C7p3nCyQ.js (removed) 137 B 🟢 -137 B 🟢 -103 B 🟢 -92 B
assets/Load3dViewerContent-Ckb055TQ.js (new) 137 B 🔴 +137 B 🔴 +103 B 🔴 +93 B
assets/WidgetBoundingBox-CP1rvKjP.js (new) 131 B 🔴 +131 B 🔴 +100 B 🔴 +91 B
assets/WidgetBoundingBox-Dsx-wh9X.js (removed) 131 B 🟢 -131 B 🟢 -100 B 🟢 -94 B
assets/missingModelMetadata-Bj3Mo5qk.js (removed) 125 B 🟢 -125 B 🟢 -103 B 🟢 -106 B
assets/missingModelMetadata-CDNoXKPu.js (new) 125 B 🔴 +125 B 🔴 +103 B 🔴 +103 B
assets/Load3DAdvanced-D-WscEKJ.js (removed) 122 B 🟢 -122 B 🟢 -97 B 🟢 -88 B
assets/Load3DAdvanced-oGN6_CJY.js (new) 122 B 🔴 +122 B 🔴 +97 B 🔴 +89 B
assets/WidgetLegacy-CCd3Exsr.js (removed) 117 B 🟢 -117 B 🟢 -106 B 🟢 -101 B
assets/WidgetLegacy-Da6SCOCk.js (new) 117 B 🔴 +117 B 🔴 +106 B 🔴 +93 B
assets/workflowDraftStoreV2-BCKlO3NA.js (new) 112 B 🔴 +112 B 🔴 +101 B 🔴 +111 B
assets/workflowDraftStoreV2-CaaEbBdI.js (removed) 112 B 🟢 -112 B 🟢 -101 B 🟢 -107 B
assets/Load3D-ChEq-yy9.js (new) 98 B 🔴 +98 B 🔴 +89 B 🔴 +78 B
assets/Load3D-CZBIO0fX.js (removed) 98 B 🟢 -98 B 🟢 -89 B 🟢 -78 B
assets/i18n-BEs9SGPq.js (new) 97 B 🔴 +97 B 🔴 +92 B 🔴 +90 B
assets/i18n-plkoqFeK.js (removed) 97 B 🟢 -97 B 🟢 -92 B 🟢 -101 B
assets/changeTracker-Dmnunl53.js (new) 91 B 🔴 +91 B 🔴 +93 B 🔴 +81 B
assets/changeTracker-DszLwRyi.js (removed) 91 B 🟢 -91 B 🟢 -93 B 🟢 -85 B

Status: 123 added / 123 removed / 162 unchanged

⚡ Performance

⏳ Performance tests in progress…

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Too many files!

This PR contains 400 files, which is 100 over the limit of 300.

To get a review, reduce the PR to 300 files or fewer by splitting it into smaller PRs or changing its base branch.

Usage-priced reviews support at most 300 files.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 78a10ae3-8982-42fd-a227-2f1018afbbe5

📥 Commits

Reviewing files that changed from the base of the PR and between a2603c5 and 907ca2b.

⛔ Files ignored due to path filters (14)
  • browser_tests/tests/domWidget.spec.ts-snapshots/focus-mode-on-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-visible-links-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/nodeBadge.spec.ts-snapshots/node-badge-left-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/primitiveNode.spec.ts-snapshots/primitive-node-connected-dom-widget-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/saveImageAndWebp.spec.ts-snapshots/save-image-and-webm-preview-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-nodes-border-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-selections-border-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-single-node-no-border-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/error.spec.ts-snapshots/vue-node-required-input-missing-slot-error-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 (400)
  • AGENTS.md
  • browser_tests/README.md
  • browser_tests/assets/missing/missing_model_nested_promoted_widget.json
  • browser_tests/assets/missing/missing_models_in_subgraph.json
  • browser_tests/assets/nodes/duplicate_node_ids.json
  • browser_tests/assets/subgraphs/subgraph-link-identity-collision.json
  • browser_tests/fixtures/VueNodeHelpers.ts
  • browser_tests/fixtures/helpers/CanvasHelper.ts
  • browser_tests/fixtures/helpers/NodeOperationsHelper.ts
  • browser_tests/fixtures/utils/nodeInputLinks.ts
  • browser_tests/tests/legacySlotMutation.spec.ts
  • browser_tests/tests/nodeBadge.spec.ts
  • browser_tests/tests/nodeDisplay.spec.ts
  • browser_tests/tests/nodeReplacement.spec.ts
  • browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts
  • browser_tests/tests/propertiesPanel/errorsTabModeAware.spec.ts
  • browser_tests/tests/rightClickMenu.spec.ts
  • browser_tests/tests/subgraph/subgraphLinkIdentity.spec.ts
  • browser_tests/tests/subgraph/subgraphPromotion.spec.ts
  • browser_tests/tests/subgraph/subgraphSerialization.spec.ts
  • browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts
  • browser_tests/tests/vueNodes/interactions/node/bringToFront.spec.ts
  • browser_tests/tests/vueNodes/interactions/node/move.spec.ts
  • browser_tests/tests/vueNodes/interactions/node/resize.spec.ts
  • browser_tests/tests/vueNodes/layout/ecsBridgeHistory.spec.ts
  • browser_tests/tests/vueNodes/layout/rendererToggleGeometry.spec.ts
  • browser_tests/tests/vueNodes/nodeStates/collapse.spec.ts
  • browser_tests/tests/vueNodes/nodeStates/registration.spec.ts
  • browser_tests/tests/vueNodes/widgets/legacy.spec.ts
  • browser_tests/tests/vueNodes/widgets/widgetReactivity.spec.ts
  • browser_tests/tests/workflowPersistence.spec.ts
  • docs/adr/0003-crdt-based-layout-system.md
  • docs/adr/0008-entity-component-system.md
  • docs/architecture/appendix-ecs-pattern-survey.md
  • docs/architecture/domain-glossary.md
  • docs/architecture/ecs-lifecycle-scenarios.md
  • docs/architecture/ecs-migration-plan.md
  • docs/architecture/ecs-target-architecture.md
  • docs/architecture/ecs/ecs-decision-traceability.md
  • docs/architecture/ecs/ecs-documentation-audit.md
  • docs/architecture/ecs/ecs-extension-compatibility-audit.md
  • docs/architecture/ecs/ecs-identity-scope-audit.md
  • docs/architecture/ecs/ecs-lifecycle-audit.md
  • docs/architecture/ecs/ecs-migration-plan.md
  • docs/architecture/ecs/ecs-migration-summary.md
  • docs/architecture/ecs/ecs-mutation-audit.md
  • docs/architecture/ecs/ecs-state-authority-audit.md
  • docs/architecture/ecs/ecs-verification-audit.md
  • docs/architecture/entity-interactions.md
  • docs/architecture/entity-problems.md
  • docs/architecture/link-topology-store.md
  • docs/architecture/node-badge-store.md
  • docs/architecture/node-data-store.md
  • docs/architecture/output-slot-connectivity.md
  • docs/architecture/proto-ecs-stores.md
  • docs/architecture/reroute-chain-store.md
  • docs/architecture/subgraph-boundaries-and-promotion.md
  • docs/extensions/README.md
  • docs/extensions/link-registration-migration.md
  • src/components/builder/AppModeWidgetList.vue
  • src/components/graph/GraphCanvas.vue
  • src/components/graph/widgets/DomWidget.test.ts
  • src/components/graph/widgets/DomWidget.vue
  • src/components/graph/widgets/domWidgetZIndex.test.ts
  • src/components/rightSidePanel/errors/useErrorGroups.test.ts
  • src/components/rightSidePanel/errors/useErrorGroups.ts
  • src/components/rightSidePanel/parameters/SectionWidgets.vue
  • src/components/rightSidePanel/parameters/TabSubgraphInputs.test.ts
  • src/components/rightSidePanel/parameters/TabSubgraphInputs.vue
  • src/components/rightSidePanel/parameters/WidgetActions.test.ts
  • src/components/rightSidePanel/parameters/WidgetActions.vue
  • src/components/rightSidePanel/parameters/WidgetItem.test.ts
  • src/components/rightSidePanel/parameters/WidgetItem.vue
  • src/components/rightSidePanel/shared.test.ts
  • src/components/rightSidePanel/subgraph/SubgraphEditor.test.ts
  • src/components/rightSidePanel/subgraph/SubgraphEditor.vue
  • src/composables/canvas/useSelectionToolboxPosition.test.ts
  • src/composables/canvas/useSelectionToolboxPosition.ts
  • src/composables/graph/useArrangeNodes.ts
  • src/composables/graph/useErrorClearingHooks.promotion.test.ts
  • src/composables/graph/useErrorClearingHooks.test.ts
  • src/composables/graph/useErrorClearingHooks.ts
  • src/composables/graph/useGraphNodeManager.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/composables/graph/useNodeArrangement.ts
  • src/composables/graph/useNodeErrorFlagSync.test.ts
  • src/composables/graph/useSelectionState.test.ts
  • src/composables/graph/useVueNodeLifecycle.ts
  • src/composables/node/badgeRendererParity.test.ts
  • src/composables/node/useNodeBadge.ts
  • src/composables/node/useNodePricing.test.ts
  • src/composables/node/useNodePricing.ts
  • src/composables/node/usePriceBadge.test.ts
  • src/composables/node/usePriceBadge.ts
  • src/composables/useUpstreamValue.test.ts
  • src/composables/useUpstreamValue.ts
  • src/core/graph/subgraph/migration/proxyWidgetMigration.test.ts
  • src/core/graph/subgraph/migration/proxyWidgetMigration.ts
  • src/core/graph/subgraph/promotedInputWidget.ts
  • src/core/graph/subgraph/promotionUtils.test.ts
  • src/core/graph/subgraph/promotionUtils.ts
  • src/core/graph/subgraph/resolveConcretePromotedWidget.test.ts
  • src/core/graph/subgraph/resolveSubgraphInputLink.ts
  • src/core/graph/widgets/dynamicWidgets.test.ts
  • src/core/graph/widgets/dynamicWidgets.ts
  • src/core/graph/widgets/matchTypeConfiguring.test.ts
  • src/extensions/core/groupNode.ts
  • src/extensions/core/load3d.ts
  • src/extensions/core/rerouteNode.ts
  • src/extensions/core/saveImageExtraOutput.test.ts
  • src/extensions/core/widgetInputs.refreshCombo.test.ts
  • src/extensions/core/widgetInputs.test.ts
  • src/extensions/core/widgetInputs.ts
  • src/extensions/core/widgetValuePropagation.test.ts
  • src/extensions/core/widgetValuePropagation.ts
  • src/lib/litegraph/src/LGraph.inputSlotRealign.test.ts
  • src/lib/litegraph/src/LGraph.serialise.test.ts
  • src/lib/litegraph/src/LGraph.test.ts
  • src/lib/litegraph/src/LGraph.ts
  • src/lib/litegraph/src/LGraphBadge.ts
  • src/lib/litegraph/src/LGraphCanvas.clipboard.test.ts
  • src/lib/litegraph/src/LGraphCanvas.cloneZIndex.test.ts
  • src/lib/litegraph/src/LGraphCanvas.deprecated.test.ts
  • src/lib/litegraph/src/LGraphCanvas.drawConnections.test.ts
  • src/lib/litegraph/src/LGraphCanvas.ghost.test.ts
  • src/lib/litegraph/src/LGraphCanvas.groupSelection.test.ts
  • src/lib/litegraph/src/LGraphCanvas.linkDragAutoPan.test.ts
  • src/lib/litegraph/src/LGraphCanvas.onMenuAdd.test.ts
  • src/lib/litegraph/src/LGraphCanvas.renderInfo.test.ts
  • src/lib/litegraph/src/LGraphCanvas.slotHitDetection.test.ts
  • src/lib/litegraph/src/LGraphCanvas.titleButtons.test.ts
  • src/lib/litegraph/src/LGraphCanvas.ts
  • src/lib/litegraph/src/LGraphGroup.test.ts
  • src/lib/litegraph/src/LGraphGroup.ts
  • src/lib/litegraph/src/LGraphNode.nodeState.test.ts
  • src/lib/litegraph/src/LGraphNode.ownCollectionFields.test.ts
  • src/lib/litegraph/src/LGraphNode.propertyChanged.test.ts
  • src/lib/litegraph/src/LGraphNode.test.ts
  • src/lib/litegraph/src/LGraphNode.ts
  • src/lib/litegraph/src/LGraphNode.widgetLabelRename.regression.test.ts
  • src/lib/litegraph/src/LGraphNodeProperties.test.ts
  • src/lib/litegraph/src/LGraphNodeProperties.ts
  • src/lib/litegraph/src/LLink.spreadCopy.test.ts
  • src/lib/litegraph/src/LLink.store.test.ts
  • src/lib/litegraph/src/LLink.test.ts
  • src/lib/litegraph/src/LLink.ts
  • src/lib/litegraph/src/LinkMap.ts
  • src/lib/litegraph/src/LiteGraphGlobal.ts
  • src/lib/litegraph/src/Reroute.store.test.ts
  • src/lib/litegraph/src/Reroute.ts
  • src/lib/litegraph/src/__fixtures__/duplicateLinks.ts
  • src/lib/litegraph/src/__fixtures__/duplicateSubgraphNodeIds.ts
  • src/lib/litegraph/src/__fixtures__/nodeHelpers.ts
  • src/lib/litegraph/src/canvas/FloatingRenderLink.test.ts
  • src/lib/litegraph/src/canvas/FloatingRenderLink.ts
  • src/lib/litegraph/src/canvas/LinkConnector.core.test.ts
  • src/lib/litegraph/src/canvas/LinkConnector.integration.test.ts
  • src/lib/litegraph/src/canvas/LinkConnector.test.ts
  • src/lib/litegraph/src/canvas/LinkConnector.ts
  • src/lib/litegraph/src/canvas/LinkConnectorSubgraphInputValidation.test.ts
  • src/lib/litegraph/src/canvas/ToInputFromIoNodeLink.ts
  • src/lib/litegraph/src/canvas/ToInputRenderLink.ts
  • src/lib/litegraph/src/canvas/findRerouteAtPoint.ts
  • src/lib/litegraph/src/canvas/getCanvasContextMenuTarget.test.ts
  • src/lib/litegraph/src/canvas/getCanvasContextMenuTarget.ts
  • src/lib/litegraph/src/idAllocation.test.ts
  • src/lib/litegraph/src/idAllocation.ts
  • src/lib/litegraph/src/infrastructure/LGraphEventMap.ts
  • src/lib/litegraph/src/infrastructure/createMutationView.test.ts
  • src/lib/litegraph/src/infrastructure/createMutationView.ts
  • src/lib/litegraph/src/interfaces.ts
  • src/lib/litegraph/src/linkDeduplication.conflictingOrigins.test.ts
  • src/lib/litegraph/src/linkDeduplication.ts
  • src/lib/litegraph/src/litegraph.ts
  • src/lib/litegraph/src/node/NodeInputSlot.test.ts
  • src/lib/litegraph/src/node/NodeInputSlot.ts
  • src/lib/litegraph/src/node/NodeOutputSlot.test.ts
  • src/lib/litegraph/src/node/NodeOutputSlot.ts
  • src/lib/litegraph/src/node/NodeSlot.test.ts
  • src/lib/litegraph/src/node/NodeSlot.ts
  • src/lib/litegraph/src/node/SlotBase.ts
  • src/lib/litegraph/src/node/legacySlotLinkMutations.test.ts
  • src/lib/litegraph/src/node/slotEcosystemPatterns.test.ts
  • src/lib/litegraph/src/node/slotLinks.test.ts
  • src/lib/litegraph/src/node/slotLinks.ts
  • src/lib/litegraph/src/node/slotUtils.test.ts
  • src/lib/litegraph/src/node/slotUtils.ts
  • src/lib/litegraph/src/node/widgetsView.test.ts
  • src/lib/litegraph/src/node/widgetsView.ts
  • src/lib/litegraph/src/nodeBadgeDraw.test.ts
  • src/lib/litegraph/src/nodeBadgeDraw.ts
  • src/lib/litegraph/src/serialization.test.ts
  • src/lib/litegraph/src/subgraph/ExecutableNodeDTO.test.ts
  • src/lib/litegraph/src/subgraph/ExecutableNodeDTO.ts
  • src/lib/litegraph/src/subgraph/SubgraphConversion.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphIO.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts
  • src/lib/litegraph/src/subgraph/SubgraphInput.ts
  • src/lib/litegraph/src/subgraph/SubgraphInputNode.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts
  • src/lib/litegraph/src/subgraph/SubgraphOutput.ts
  • src/lib/litegraph/src/subgraph/SubgraphSerialization.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphSlotConnections.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphWidgetPromotion.test.ts
  • src/lib/litegraph/src/subgraph/__fixtures__/README.md
  • src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.test.ts
  • src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts
  • src/lib/litegraph/src/subgraph/subgraphDeduplication.test.ts
  • src/lib/litegraph/src/subgraph/subgraphDeduplication.ts
  • src/lib/litegraph/src/subgraph/subgraphUtils.test.ts
  • src/lib/litegraph/src/subgraph/subgraphUtils.ts
  • src/lib/litegraph/src/types/graphTriggers.ts
  • src/lib/litegraph/src/types/serialisation.ts
  • src/lib/litegraph/src/types/widgets.ts
  • src/lib/litegraph/src/utils/collections.test.ts
  • src/lib/litegraph/src/utils/collections.ts
  • src/lib/litegraph/src/utils/widget.ts
  • src/lib/litegraph/src/widgetValueNullContract.test.ts
  • src/lib/litegraph/src/widgets/BaseWidget.test.ts
  • src/lib/litegraph/src/widgets/BaseWidget.ts
  • src/locales/ar/main.json
  • src/locales/en/main.json
  • src/locales/es/main.json
  • src/locales/fa/main.json
  • src/locales/fr/main.json
  • src/locales/he/main.json
  • src/locales/ja/main.json
  • src/locales/ko/main.json
  • src/locales/pt-BR/main.json
  • src/locales/ru/main.json
  • src/locales/tr/main.json
  • src/locales/zh-TW/main.json
  • src/locales/zh/main.json
  • src/platform/cloud/subscription/composables/useFreeTierQuota.ts
  • src/platform/missingMedia/__fixtures__/promotedMedia.ts
  • src/platform/missingMedia/missingMediaScan.test.ts
  • src/platform/missingMedia/missingMediaStore.ts
  • src/platform/missingModel/missingModelScan.test.ts
  • src/platform/missingModel/missingModelScan.ts
  • src/platform/missingModel/missingModelStore.ts
  • src/platform/nodeReplacement/useNodeReplacement.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.ts
  • src/platform/telemetry/nodeAdded/installNodeAddedTelemetry.test.ts
  • src/platform/telemetry/nodeAdded/installNodeAddedTelemetry.ts
  • src/platform/workflow/core/services/workflowService.insertWorkflow.test.ts
  • src/platform/workflow/core/services/workflowService.ts
  • src/platform/workflow/core/utils/workflowToClipboardItems.integration.test.ts
  • src/platform/workflow/core/utils/workflowToClipboardItems.test.ts
  • src/platform/workflow/core/utils/workflowToClipboardItems.ts
  • src/platform/workflow/validation/schemas/workflowSchema.test.ts
  • src/renderer/core/canvas/canvasStore.test.ts
  • src/renderer/core/canvas/canvasStore.ts
  • src/renderer/core/canvas/links/linkConnectorAdapter.ts
  • src/renderer/core/canvas/links/linkDropOrchestrator.test.ts
  • src/renderer/core/canvas/litegraph/arrangeForLegacyRender.test.ts
  • src/renderer/core/canvas/litegraph/arrangeForLegacyRender.ts
  • src/renderer/core/canvas/litegraph/notifyLayoutChanges.test.ts
  • src/renderer/core/canvas/litegraph/notifyLayoutChanges.ts
  • src/renderer/core/canvas/litegraph/slotCalculations.test.ts
  • src/renderer/core/canvas/litegraph/slotCalculations.ts
  • src/renderer/core/layout/constants.ts
  • src/renderer/core/layout/operations/graphLayoutAttachment.ts
  • src/renderer/core/layout/operations/layoutMutations.test.ts
  • src/renderer/core/layout/operations/layoutMutations.ts
  • src/renderer/core/layout/store/__mocks__/layoutStore.ts
  • src/renderer/core/layout/store/batchUpdateNodeBounds.test.ts
  • src/renderer/core/layout/store/hitTargetAuthority.test.ts
  • src/renderer/core/layout/store/layoutStore.multiHolder.test.ts
  • src/renderer/core/layout/store/layoutStore.test.ts
  • src/renderer/core/layout/store/layoutStore.ts
  • src/renderer/core/layout/sync/syncLayoutStoreFromGraph.test.ts
  • src/renderer/core/layout/sync/syncLayoutStoreFromGraph.ts
  • src/renderer/core/layout/sync/useLayoutSync.test.ts
  • src/renderer/core/layout/sync/useLayoutSync.ts
  • src/renderer/core/layout/types.ts
  • src/renderer/core/layout/utils/mappers.test.ts
  • src/renderer/core/layout/utils/mappers.ts
  • src/renderer/extensions/firstRunTour/roles/heuristicRoles.test.ts
  • src/renderer/extensions/firstRunTour/roles/heuristicRoles.ts
  • src/renderer/extensions/firstRunTour/tour/canvasCoachTarget.test.ts
  • src/renderer/extensions/firstRunTour/tour/canvasCoachTarget.ts
  • src/renderer/extensions/firstRunTour/tour/firstRunTourDefinition.test.ts
  • src/renderer/extensions/layerEditor/engine/editor/selectionMath.test.ts
  • src/renderer/extensions/linearMode/PartnerNodesList.vue
  • src/renderer/extensions/minimap/composables/useMinimap.intervalLifecycle.test.ts
  • src/renderer/extensions/minimap/composables/useMinimap.test.ts
  • src/renderer/extensions/minimap/composables/useMinimapGraph.test.ts
  • src/renderer/extensions/minimap/composables/useMinimapGraph.ts
  • src/renderer/extensions/minimap/composables/useMinimapViewport.test.ts
  • src/renderer/extensions/minimap/composables/useMinimapViewport.ts
  • src/renderer/extensions/minimap/data/AbstractMinimapDataSource.ts
  • src/renderer/extensions/minimap/data/LayoutStoreDataSource.ts
  • src/renderer/extensions/minimap/data/LiteGraphDataSource.ts
  • src/renderer/extensions/minimap/data/MinimapDataSource.test.ts
  • src/renderer/extensions/minimap/data/MinimapDataSource.ts
  • src/renderer/extensions/minimap/data/MinimapDataSourceFactory.ts
  • src/renderer/extensions/minimap/minimapCanvasRenderer.test.ts
  • src/renderer/extensions/minimap/minimapCanvasRenderer.ts
  • src/renderer/extensions/minimap/types.ts
  • src/renderer/extensions/vueNodes/components/LGraphNode.subgraph.test.ts
  • src/renderer/extensions/vueNodes/components/LGraphNode.test.ts
  • src/renderer/extensions/vueNodes/components/LGraphNode.vue
  • src/renderer/extensions/vueNodes/components/LGraphNodePreview.test.ts
  • src/renderer/extensions/vueNodes/components/LGraphNodePreview.vue
  • src/renderer/extensions/vueNodes/components/NodeContent.vue
  • src/renderer/extensions/vueNodes/components/NodeHeader.test.ts
  • src/renderer/extensions/vueNodes/components/NodeHeader.vue
  • src/renderer/extensions/vueNodes/components/NodeSlots.test.ts
  • src/renderer/extensions/vueNodes/components/NodeSlots.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/components/WidgetGrid.vue
  • src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts
  • src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts
  • src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts
  • src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts
  • src/renderer/extensions/vueNodes/composables/useNodeTooltips.test.ts
  • src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts
  • src/renderer/extensions/vueNodes/composables/useNodeZIndex.test.ts
  • src/renderer/extensions/vueNodes/composables/useNodeZIndex.ts
  • src/renderer/extensions/vueNodes/composables/usePartitionedBadges.test.ts
  • src/renderer/extensions/vueNodes/composables/usePartitionedBadges.ts
  • src/renderer/extensions/vueNodes/composables/useProcessedWidgets.test.ts
  • src/renderer/extensions/vueNodes/composables/useProcessedWidgets.ts
  • src/renderer/extensions/vueNodes/composables/useSlotElementTracking.test.ts
  • src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts
  • src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.autoPan.test.ts
  • src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts
  • src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.test.ts
  • src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts
  • src/renderer/extensions/vueNodes/interactions/resize/useNodeResize.test.ts
  • src/renderer/extensions/vueNodes/interactions/resize/useNodeResize.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts
  • src/renderer/extensions/vueNodes/layout/nodeSizeReflow.test.ts
  • src/renderer/extensions/vueNodes/layout/useNodeDrag.test.ts
  • src/renderer/extensions/vueNodes/layout/useNodeDrag.ts
  • src/renderer/extensions/vueNodes/layout/useNodeLayout.test.ts
  • src/renderer/extensions/vueNodes/layout/useNodeLayout.ts
  • src/renderer/extensions/vueNodes/types/widgetGrid.ts
  • src/renderer/extensions/vueNodes/utils/__tests__/nodeDataUtils.test.ts
  • src/renderer/extensions/vueNodes/utils/nodeDataUtils.ts
  • src/renderer/extensions/vueNodes/utils/nodeErrorState.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetButton.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetDOM.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetDOM.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useProgressTextWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/renderer/glsl/glslPreviewUtils.ts
  • src/renderer/glsl/useGLSLUniforms.test.ts
  • src/renderer/glsl/useGLSLUniforms.ts
  • src/scripts/app.test.ts
  • src/scripts/app.ts
  • src/scripts/changeTracker.test.ts
  • src/scripts/pnginfo.test.ts
  • src/scripts/promotedWidgetControl.test.ts
  • src/scripts/promotedWidgetControl.ts
  • src/scripts/widgets.ts
  • src/services/litegraphService.ts
  • src/services/subgraphService.ts
  • src/stores/appModeStore.test.ts
  • src/stores/linkStore.test.ts
  • src/stores/linkStore.ts
  • src/stores/nodeDataStore.test.ts
  • src/stores/nodeDataStore.ts
  • src/stores/rerouteStore.test.ts
  • src/stores/rerouteStore.ts
  • src/stores/widgetValueStore.graphReactivity.test.ts
  • src/stores/widgetValueStore.test.ts
  • src/stores/widgetValueStore.ts
  • src/stores/workspace/favoritedWidgetsStore.ts
  • src/systems/badgeSystem.pricing.test.ts
  • src/systems/badgeSystem.subgraph.test.ts
  • src/systems/badgeSystem.test.ts
  • src/systems/badgeSystem.ts
  • src/types/badgeData.ts
  • src/types/graphScopeId.ts
  • src/types/groupId.ts
  • src/types/linkTopology.ts
  • src/types/nodeState.ts
  • src/types/rerouteChain.ts
  • src/types/simplifiedWidget.ts
  • src/types/widgetState.ts
  • src/utils/__tests__/litegraphTestUtils.ts
  • src/utils/executionUtil.test.ts
  • src/utils/graphTraversalUtil.test.ts
  • src/utils/graphTraversalUtil.ts
  • src/utils/linkFixer.test.ts
  • src/utils/linkFixer.ts
  • src/utils/litegraphUtil.test.ts
  • src/utils/litegraphUtil.ts
  • src/utils/searchAndReplace.test.ts
  • src/utils/vintageClipboard.ts
  • src/utils/widgetUtil.ts
  • tools/devtools/web/legacyWidget.js

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

📝 Walkthrough

Walkthrough

This PR migrates LiteGraph's link, reroute, and node topology from mutable slot mirrors (input.link, output.links, _floatingLinks) into dedicated Pinia stores (linkStore, rerouteStore, nodeDataStore). It deprecates direct slot mutation. It adds a badgeSystem that derives node badges, including pricing and credits, on read. This replaces usePriceBadge, inline useNodeBadge logic, and BadgePosition. It removes useGraphNodeManager and useVueNodeLifecycle in favor of NodeState-driven Vue node rendering. It updates widget promotion and dynamic-widget code to use graph-level link helpers. It switches node lifecycle notifications to node:added/node:removed events. Extensive tests, docs, and locale updates accompany the change.

Estimated code review effort: 5 (Critical) | ~180+ minutes

Changes

Core ECS store migration (link/reroute/node-data/badge stores)

Layer / File(s) Summary
Link topology, reroute, and node-data stores
src/stores/linkStore.ts, src/stores/rerouteStore.ts, src/stores/nodeDataStore.ts, src/types/linkTopology.ts, src/types/rerouteChain.ts, src/types/nodeState.ts, tests
New Pinia stores hold link endpoints, reroute chain membership, and per-graph node state as the authoritative topology source. This replaces mutable slot fields.
LiteGraph core class refactor
src/lib/litegraph/src/LLink.ts, Reroute.ts, LGraph.ts, LGraphNode.ts, LGraphGroup.ts, LGraphCanvas.ts, canvas/LinkConnector.ts, node/*, subgraph files, tests
LLink, Reroute, and LGraphNode now back state through _state and store registration. Slot helper functions (inputLinkId, outputLinks, slotFloatingLinks) replace direct field access. Deprecated accessors warn on legacy read or write.
Node lifecycle events and replacement
useErrorClearingHooks.ts, useNodeReplacement.ts, installNodeAddedTelemetry.ts, workflowService.ts, workflowToClipboardItems.ts
Node add and remove notifications move from callback overriding to graph.events dispatch (node:added/node:removed). Workflow insertion uses clipboard-item conversion instead of a temporary canvas.
Widget promotion and dynamic widgets
promotionUtils.ts, proxyWidgetMigration.ts, dynamicWidgets.ts, widgetInputs.ts, widgetValuePropagation.ts, rerouteNode.ts, groupNode.ts
Promotion, migration, and dynamic-widget code resolve connections through link-store and graph helpers instead of raw slot arrays. Input layout replacement is centralized through replaceNodeInputs and captureInputLayout.
Promoted-widget UI (host prop)
SectionWidgets.vue, TabSubgraphInputs.vue, WidgetActions.vue, WidgetItem.vue, SubgraphEditor.vue, AppModeWidgetList.vue, useUpstreamValue.ts
Components accept a single host subgraph node prop instead of parents/isShownOnParents. Linkage checks use linkStore.isInputSlotConnected.
Badge derivation system
src/systems/badgeSystem.ts, src/types/badgeData.ts, nodeBadgeDraw.ts, useNodeBadge.ts, useNodePricing.ts, usePartitionedBadges.ts, PartnerNodesList.vue, useFreeTierQuota.ts
badgeSystem.computeBadges derives core and credits badge rows per node on read. usePriceBadge and BadgePosition are removed. Pricing caching keys on connection state through isInputConnected.
Vue node rendering and layout/minimap
GraphCanvas.vue, LGraphNode.vue, NodeSlots.vue, NodeWidgets.vue, WidgetGrid.vue, useProcessedWidgets.ts, layoutStore.ts, layoutMutations.ts, minimap data sources
Vue node components consume NodeState from nodeDataStore. The layout store and mutations become graph-scoped (rootGraphId). Minimap data sources read links and nodes from the new stores.
Widget value store and utilities
widgetValueStore.ts, graphTraversalUtil.ts, linkFixer.ts, litegraphUtil.ts, missingModelScan.ts, locale files
widgetValueStore gains render-state tracking and node widget ordering. Graph traversal and link-fixer utilities and locale strings (hideInput to advanced-input labels) are updated to match the new store model.

Sequence Diagram(s)

sequenceDiagram
  participant LGraphNode as LGraphNode (connect)
  participant LGraph as LGraph
  participant LinkStore as linkStore (Pinia)
  participant RerouteStore as rerouteStore (Pinia)
  participant LayoutStore as layoutStore (Pinia)

  LGraphNode->>LGraph: _addLink(link)
  LGraph->>LinkStore: registerLink(graphId, topology)
  LGraph->>RerouteStore: anchorRerouteChain(link)
  RerouteStore->>LinkStore: derive membership from parentId chain
  Note over LGraphNode,LinkStore: input.link / output.links no longer written directly
  LGraphNode->>LGraph: disconnectInput/disconnectOutput
  LGraph->>LinkStore: deleteLink / unregisterLinkTopology
  LGraph->>LayoutStore: deleteLinkLayout(linkId)
Loading
sequenceDiagram
  participant Node as LGraphNode
  participant Events as graph.events
  participant Hooks as useErrorClearingHooks
  participant Telemetry as installNodeAddedTelemetry

  Node->>Events: dispatch('node:added', { node })
  Events->>Hooks: node:added listener
  Hooks->>Hooks: install per-node error hooks
  Events->>Telemetry: node:added listener
  Telemetry->>Telemetry: trackNodeAdded(node)
Loading

Possibly related PRs

Suggested labels: size:XXL, area:litegraph, area:widgets, area:vue-nodes

Suggested reviewers: austinmroz

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Adr Compliance For Entity/Litegraph Changes ⚠️ Warning NodeState is stored in nodeDataStore but its inputs/outputs are behavior-bearing INode*Slot instances with node back-references, violating ADR 0008 plain-data component separation (docs/adr/0008-en... Keep behavior-bearing slots outside NodeState, or replace them with plain slot-state rows and resolve node behavior through an adapter, as required by ADR 0008.
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
End-To-End Regression Coverage For Fixes ✅ Passed The PR changes many src/ files and also changes multiple browser_tests/ files, so condition 2 is false regardless of any bug-fix wording.
Title check ✅ Passed The title clearly identifies the central ECS migration, although “Still Draft Feature” adds minor status noise.
Description check ✅ Passed The description covers the migration summary, key changes, review focus, status, and relevant context, with screenshots omitted appropriately for a non-visual change.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feature/ecs-migration
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ecs-migration

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

@DrJKL DrJKL added the New Browser Test Expectations New browser test screenshot should be set by github action label Jul 29, 2026
@github-actions

Copy link
Copy Markdown

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 Jul 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 49

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/components/NodeSlots.vue (1)

10-19: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Collapse the per-slot lookups into one computed row list.

getActualInputIndex now runs three times per input during render (:key, :index, and inside :connected), each a linear findIndex over nodeData.inputs — O(N²) per node render on the canvas hot path. Building the rows once also removes the duplicated rootGraphId guards in isInputConnected/isOutputConnected.

♻️ Sketch
const inputRows = computed(() => {
  const graphId = canvasStore.rootGraphId
  return filteredInputs.value.map((input, filteredIndex) => {
    const actualIndex = nodeData.inputs.indexOf(input)
    const index = actualIndex !== -1 ? actualIndex : filteredIndex
    return {
      input,
      index,
      connected: graphId
        ? linkStore.isInputSlotConnected(graphId, nodeData.id, index)
        : false
    }
  })
})
-      <InputSlot
-        v-for="(input, index) in filteredInputs"
-        :key="`input-${input.name}-${getActualInputIndex(input, index)}`"
-        :slot-data="input"
-        :node-type="nodeData?.type || ''"
-        :node-id="nodeData.id"
-        :has-error="inputHasError(input)"
-        :index="getActualInputIndex(input, index)"
-        :connected="isInputConnected(getActualInputIndex(input, index))"
-      />
+      <InputSlot
+        v-for="row in inputRows"
+        :key="`input-${row.input.name}-${row.index}`"
+        :slot-data="row.input"
+        :node-type="nodeData.type"
+        :node-id="nodeData.id"
+        :has-error="inputHasError(row.input)"
+        :index="row.index"
+        :connected="row.connected"
+      />

Also applies to: 119-125

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/extensions/vueNodes/components/NodeSlots.vue` around lines 10 -
19, In NodeSlots.vue, add computed inputRows and outputRows that resolve each
slot’s actual index once and calculate connection state using the rootGraphId
within the computed mapping. Update the InputSlot and OutputSlot v-for blocks to
consume these row objects for keys, slot data, indices, and connected state,
removing repeated getActualInputIndex calls and redundant rootGraphId guards
from isInputConnected/isOutputConnected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts`:
- Around line 1360-1365: Update the link-resolution logic near the Preview Image
node to stop reading the removed inputs[0].link mirror. Resolve the node’s
connected link through the graph topology API or graph.links, then use that
link’s identifier with getLink to obtain parentId while preserving the existing
failure messages.

In `@docs/architecture/entity-interactions.md`:
- Around line 106-108: Update the Legend in entity-interactions.md to define the
-.- edge style used by the derived linkIds and floatingLinkIds relationships,
including its meaning as a dotted, non-directional derived relationship. Keep
the existing owns, references, and extends legend entries unchanged.

In `@docs/architecture/link-topology-store.md`:
- Around line 18-21: Add language annotations to both new fenced code blocks:
use an appropriate tag such as ts or text for the LinkTopology shape in
docs/architecture/link-topology-store.md lines 18-21, and ts for the BadgeData
union in docs/architecture/node-badge-store.md lines 40-44, resolving
markdownlint MD040 without changing the documented content.

In `@docs/architecture/node-data-store.md`:
- Around line 28-43: Add the TypeScript language identifier to the NodeState
fenced code block in the architecture documentation, changing the opening fence
to use ts while leaving the schema content unchanged.

In `@docs/architecture/proto-ecs-stores.md`:
- Around line 388-413: Update the migration matrix and priority entries to
reflect the implemented state: add NodeDataStore-extracted data to the Node row,
remove stale input.link/output.links mirror work from the Link and Slot gaps,
and adjust the related priority descriptions accordingly. Use the existing
NodeDataStore and output-slot-connectivity documentation as the source of truth
while preserving the remaining unimplemented extraction work.

In `@src/components/builder/AppModeWidgetList.vue`:
- Around line 89-110: Make mappedSelections a pure computed by removing
ensureSelectedWidgetState from its getter and register resolved widgets in a
watch/watchEffect driven by resolvedInputs instead. In the computed, check
isWidgetInputLinked before calling nodeToNodeData so filtered linked inputs do
not build unused data; preserve the existing selection mapping for unlinked
ALWAYS-mode entries.

In `@src/composables/graph/useNodeErrorFlagSync.test.ts`:
- Around line 32-34: Restore Vitest mocks before each test in the setup for this
suite, alongside setActivePinia, so the rootGraph and isGraphReady getter spies
created by setupGraphWithStore and the subgraph tests do not accumulate across
it blocks. Use the project’s required reset/restore pattern consistently for all
tests.

In `@src/composables/graph/useVueNodeLifecycle.ts`:
- Around line 94-99: Update disposeVueNodeLayout to clear all node entries from
layoutStore after stopping synchronization and listeners, undoing the data
seeded by initializeVueNodeLayout. Ensure the shouldRenderVueNodes transition
handled by the whenever block leaves layoutStore empty when Vue rendering is
disabled, preserving the existing cleanup state resets.

In `@src/composables/node/useNodePricing.ts`:
- Around line 589-593: The cache-miss fallback in the node pricing evaluation
flow must use the previous label for the current signature rather than the
newest label from any cached signature. Update the state associated with
scheduleEvaluation or the surrounding caller to track and return the last label
per signature/read context, while preserving the empty-string fallback when no
matching prior label exists.

In `@src/core/graph/subgraph/promotionUtils.ts`:
- Around line 298-308: Replace the direct promotedState.label mutation in the
hostInput promotion block with the widget store’s dedicated label-update action,
adding that action if none exists. Pass hostInput.widgetId and sourceSlot.label
through the store API so state invariants, telemetry, and persistence hooks are
preserved.

In `@src/core/graph/widgets/dynamicWidgets.test.ts`:
- Around line 10-11: Remove the redundant module-level
setActivePinia(createTestingPinia({ stubActions: false })) call in
dynamicWidgets.test.ts, keeping the beforeEach setup as the sole initialization
so each test receives a fresh testing Pinia.

In `@src/core/graph/widgets/dynamicWidgets.ts`:
- Around line 269-276: Replace the deprecated indexed link lookup in the
topology loop with the graph’s store-consistent getLink() method, using
topology.id as the lookup key and preserving the existing missing-link continue
behavior.
- Around line 555-563: Move the syncNodeWidgetOrder(node) call out of the
toRemove iteration and invoke it once after the loop completes. Preserve the
existing widget removal and cleanup behavior, including skipping inputs without
a widget name.

In `@src/extensions/core/widgetInputs.test.ts`:
- Around line 19-37: Remove the explicit 30_000 ms timeout argument from the
synchronous test defined by “resets itself when the store reports a link the
graph cannot resolve,” leaving the test body and its assertions unchanged so
Vitest uses its default timeout.

In `@src/extensions/core/widgetValuePropagation.ts`:
- Around line 25-28: Update the early-return guard in the widget value
propagation function to account for both store links and extraLinks, allowing
propagation when extraLinks is non-empty even if linked is empty. Keep returning
early only when neither source contains endpoints, and preserve the existing
endpoint-merging behavior.

In `@src/lib/litegraph/src/__fixtures__/nodeHelpers.ts`:
- Around line 38-55: Update createTestWidgetNode to register an onTestFinished
cleanup alongside its WIDGET_NODE_TYPE registration, restoring the previous
LiteGraph.registered_node_types entry or removing the test type after the test
completes, matching createTestNode’s cleanup pattern. Keep node creation and
graph.add behavior unchanged.

In `@src/lib/litegraph/src/LGraph.inputSlotRealign.test.ts`:
- Around line 319-322: Update the parameter list for the parameterized test
“rekeys a serialized %s” to destructure each it.for tuple into its name and
usePurgedAlias values, ensuring both registered-link and purged-alias cases
execute with their intended boolean.

In `@src/lib/litegraph/src/LGraphCanvas.ts`:
- Line 5706: Update the clip-path shape selection near drawNodeShape to use
node.renderingShape instead of node.shape or a direct BOX fallback, keeping
clipping consistent with the shape resolved by the node body and its
class/default fallbacks.

In `@src/lib/litegraph/src/LGraphNode.test.ts`:
- Around line 878-899: Remove the redundant “defaults to a normal title” test
because it only checks constructor initialization. In the remaining “titleMode
in node state” test, avoid the private _state shape by asserting the NO_TITLE
value through the store-held state returned by getGraphNodesFor(...) or the
public title_mode accessor, while preserving coverage that TitlelessNode’s
static title_mode is propagated.

In `@src/lib/litegraph/src/LGraphNode.ts`:
- Around line 347-349: Make the _state accessors consistent by routing the id
setter, flags setter, and resizable setter through setTrackedState, ensuring
each emits node:property:changed like title, mode, color, bgcolor, and
showAdvanced. Update the affected setters and preserve their existing values and
behavior, including resizable changes triggered by pin() and configure().
- Around line 4023-4031: Optimize updateComputedDisabled to avoid repeated
inputs scans during rendering: update getSlotFromWidget or the surrounding
widget loop to return/reuse the matching input index and determine connectivity
directly from that result, rather than calling this.inputs.indexOf(slot) after
the lookup. Preserve the existing computedDisabled behavior for widgets without
slots, disabled widgets, and connected inputs.
- Around line 1149-1157: The output serialization mapping in LGraphNode
serialization currently masks a type mismatch with `@ts-expect-error`. Remove that
directive and reconcile the return type of outputAsSerialisable with the
ISserialisedNode outputs element type so the mapping type-checks without
suppression.
- Around line 1062-1070: Update the input restoration loop in configure() to
invoke onConnectionsChange only when the serialized input contains a link that
was successfully restored; skip the callback for bare inputs, matching the
output restoration behavior. Continue invoking onInputAdded for every input.
- Around line 1884-1911: Update removeInput so the replaceNodeInputs failure
path, identified when this.inputs still includes slotInfo, logs a warning before
returning. Keep the existing removal callback, floating-link adjustment, and
canvas-dirty behavior unchanged for successful removals.

In `@src/lib/litegraph/src/linkDeduplication.ts`:
- Around line 54-72: Remove the unused link lookup and its guard from
purgeOrphanedLinks; iterate over non-survivor ids and call graph._removeLink(id)
directly, preserving the survivor re-registration logic afterward.

In `@src/lib/litegraph/src/LLink.ts`:
- Around line 98-112: Update applyEndpointPatch to propagate failures from
useLinkStore().updateEndpoint instead of only logging result.error and returning
normally. Throw or otherwise surface the rejected patch through the existing
endpoint setter/configure call chain, while preserving direct Object.assign
behavior for unregistered links.

In `@src/lib/litegraph/src/node/slotLinks.ts`:
- Around line 156-158: Collapse the short single-statement if block in the
finalInputs validation by placing the condition and throw on one line,
preserving the existing duplicate-slot check and error message.

In `@src/lib/litegraph/src/node/slotUtils.test.ts`:
- Around line 32-43: Update the ordering assertion in the “serialises the links
leaving the slot, ascending by id” test to sort LinkId values with an explicit
numeric comparator. Keep the existing length assertion and verify
serialised.links against its numerically ascending copy.

In `@src/lib/litegraph/src/nodeBadgeDraw.ts`:
- Around line 76-86: The registerBadgeRowsProvider function permanently pins the
first provider and offers no cleanup path. Add an unregister/disposer mechanism
tied to the registered provider, or expose a reset API for HMR and tests, while
preserving rejection of conflicting active registrations.
- Around line 52-62: Update the badge construction loop to create the credits
icon only when row.kind is exactly 'credits', rather than treating every
non-core row as credits. Preserve core-row skipping and ensure other BadgeData
kinds are handled explicitly or rejected by the type system instead of silently
receiving the credits icon.

In `@src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts`:
- Around line 68-95: Update enableSubgraphNodeCreation to keep the event
listener and type-to-constructor registrations local to each helper instance.
Make the returned disposer remove its own listener and delete only registrations
whose current constructor still matches the locally stored constructor,
preventing helpers from affecting each other; add coverage that creates another
subgraph after disposal and verifies no registration occurs.

In `@src/lib/litegraph/src/subgraph/subgraphDeduplication.ts`:
- Around line 114-129: Parameterize findNextAvailableId with an identifier-space
label and use it in the exhaustion error instead of the hardcoded node-specific
message. Update each caller, including node and reroute ID allocation, to
provide the appropriate label while preserving the existing allocation behavior.

In `@src/lib/litegraph/src/subgraph/subgraphUtils.test.ts`:
- Line 3: Update the tests in subgraphUtils.test.ts to restore mocked globals
after every test, using an afterEach hook with vi.restoreAllMocks(). Ensure
console.error is restored even when assertions fail, and remove any reliance on
cleanup that only runs after successful assertions.

In `@src/platform/cloud/subscription/composables/useFreeTierQuota.ts`:
- Around line 28-31: Update hasInvalidNodes in useFreeTierQuota to derive the
root graph from useCanvasStore().currentGraph instead of the non-reactive
app.graph, preserving the existing graphCreditsBadges check and false fallback.
Ensure the composable accesses the reactive currentGraph source so
freeTierExecutionPermitted recalculates after workflow switches.

In `@src/renderer/extensions/minimap/data/MinimapDataSource.test.ts`:
- Around line 48-59: Update registerNodeState so useNodeDataStore().registerNode
receives the graphId parameter as its store partition key instead of the
hardcoded GRAPH_ID, while continuing to set the same graphId on the created node
state.

In `@src/renderer/extensions/vueNodes/components/LGraphNode.vue`:
- Around line 702-711: Update the widgetIds computed in LGraphNode.vue to derive
graph identity from the reactive canvasStore.rootGraphId instead of
app.rootGraph?.id. Preserve the existing empty-result guard and getNodeWidgetIds
lookup, ensuring hasRenderableWidgets and downstream computeds refresh when the
root graph is replaced.

In `@src/renderer/extensions/vueNodes/components/NodeHeader.test.ts`:
- Around line 22-32: Replace the local makeNodeData fixture with the shared
createNodeState fixture from litegraphTestUtils, adding the required import and
passing the existing overrides through it. Remove the hand-rolled NodeState
defaults so shared fields, including the ALWAYS mode, remain consistent.

In `@src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts`:
- Around line 265-267: Replace the Tailwind class assertion in the NodeWidgets
test with an assertion on a semantic error signal exposed by the widget row,
such as data-has-error or aria-invalid. Update the relevant widget-row rendering
symbol to provide that signal if needed, while preserving the existing error
behavior.

In `@src/renderer/extensions/vueNodes/composables/useProcessedWidgets.test.ts`:
- Around line 342-351: Update the test around processWidgets to avoid asserting
the literal Tailwind value ring ring-component-node-widget-advanced. Verify the
advanced/non-advanced borderStyle behavior by comparing results for showAdvanced
or reuse a shared source constant, and apply the same change to the duplicate
assertion near the other advanced-widget test.

In `@src/renderer/extensions/vueNodes/composables/useProcessedWidgets.ts`:
- Around line 411-414: Update the valueTooltip logic in useProcessedWidgets so
object-valued asset/combo widgets do not produce a tooltip from String(value),
particularly avoiding “[object Object]”. Only attach the tooltip when the value
is a meaningful primitive string representation longer than 10 characters, while
preserving the existing type and length checks.

In `@src/renderer/glsl/useGLSLUniforms.test.ts`:
- Around line 21-32: Update makeGlslNode to use a real Subgraph and LiteGraph
links from the shared test factories instead of overriding getInputLink with a
hand-rolled inputs-to-subgraph mapping. Remove the getInputLink mock so
extractUniformSources exercises the actual LGraphNode.getInputLink behavior,
while preserving the test’s intended input-link setup.

In `@src/services/litegraphService.ts`:
- Around line 467-485: Extract the duplicated serialized-output merge into a
module-level helper, such as mergeSerialisedOutputs, preserving the existing
zip, RESERVED_KEYS merge, extra-output handling, and outputAsSerialisable
fallback. Replace the inline blocks in src/services/litegraphService.ts:467-485
and src/services/litegraphService.ts:579-597 with calls to the shared helper.

In `@src/stores/widgetValueStore.graphReactivity.test.ts`:
- Around line 279-293: Strengthen the test “registers plain render metadata for
non-promoted widgets” by replacing the definedness and absent sourceExecutionId
checks with assertions for the expected non-promoted render metadata, including
hasLayoutSize: false and isDOMWidget: false. Ensure the assertions distinguish
plain registration from promoted widget registration rather than only checking
defaults.

In `@src/stores/widgetValueStore.ts`:
- Around line 146-151: Update getWidgetRenderState to validate widgetId with the
existing isWidgetId guard before calling parseWidgetId; return undefined for
non-conforming IDs, matching getWidget’s behavior, while preserving the current
graph-state lookup for valid IDs.
- Around line 61-69: Split lazy creation from reads in the widget-order store:
add an ensure helper for mutation paths and a non-mutating peek helper for
reads. Update appendNodeWidgetOrder and setNodeWidgetOrder to use
ensureNodeWidgetOrder, and getNodeWidgetIds and reconcileNodeWidgetOrder to use
peekNodeWidgetOrder so computed reads do not insert entries. Apply the same
ensure/peek separation to getWidgetRenderState, ensuring read access does not
create per-graph maps.

In `@src/stores/workspace/favoritedWidgetsStore.ts`:
- Around line 76-79: Track the deferred migration from (nodeLocatorId,
widgetName) keys to host-scoped WidgetId keys by opening an issue that documents
the favoritedWidgets persistence-format change, required one-time migration of
workflow.extra.favoritedWidgets, and removal of the
isShownOnParents/favoriteNode indirection for promoted widgets.

In `@src/systems/badgeSystem.pricing.test.ts`:
- Around line 13-22: The getNodeDisplayPrice mock currently reads
useLinkStore().isInputSlotConnected, allowing the badge computed to react
without touchPricingSources registering the dependency. Make getNodeDisplayPrice
pure by deriving its result from node data or a plain test-controlled
counter/override, then update the test setup to flip that value and assert
recomputation specifically through touchPricingSources.

In `@src/systems/badgeSystem.ts`:
- Around line 212-220: Update the Settings map entries for
Comfy.NodeBadge.NodeIdBadgeMode, Comfy.NodeBadge.NodeLifeCycleBadgeMode, and
Comfy.NodeBadge.NodeSourceBadgeMode to use NodeBadgeMode, then remove the
corresponding assertions from the badgeModes initialization. Ensure
settingStore.get infers and returns NodeBadgeMode directly for these keys.
- Around line 167-187: Update gatherSubgraphCredits to touch all dynamic pricing
dependencies for the single inner API leaf, not only
pricing.getNodeRevisionRef(leaf.id). Reuse the dependency-registration behavior
from touchPricingSources, passing the appropriate inner graph-id keys from
wrapper.subgraph for priced widget values and input connections, while
preserving the existing multi-leaf and display-price behavior.

---

Outside diff comments:
In `@src/renderer/extensions/vueNodes/components/NodeSlots.vue`:
- Around line 10-19: In NodeSlots.vue, add computed inputRows and outputRows
that resolve each slot’s actual index once and calculate connection state using
the rootGraphId within the computed mapping. Update the InputSlot and OutputSlot
v-for blocks to consume these row objects for keys, slot data, indices, and
connected state, removing repeated getActualInputIndex calls and redundant
rootGraphId guards from isInputConnected/isOutputConnected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 77516a24-b905-4d0c-b70f-f93cbf262d19

📥 Commits

Reviewing files that changed from the base of the PR and between 4c643e9 and 96ba092.

⛔ Files ignored due to path filters (14)
  • browser_tests/tests/domWidget.spec.ts-snapshots/focus-mode-on-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/execution.spec.ts-snapshots/execution-error-unconnected-slot-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-visible-links-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-default-workflow-mobile-chrome-linux.png is excluded by !**/*.png
  • browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-graph-canvas-toolbar-mobile-chrome-linux.png is excluded by !**/*.png
  • browser_tests/tests/nodeBadge.spec.ts-snapshots/node-badge-left-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/saveImageAndWebp.spec.ts-snapshots/save-image-and-webm-preview-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-nodes-border-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-selections-border-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-single-node-no-border-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/viewport.spec.ts-snapshots/viewport-fits-when-saved-offscreen-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-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 (272)
  • browser_tests/assets/missing/missing_model_nested_promoted_widget.json
  • browser_tests/assets/missing/missing_models_in_subgraph.json
  • browser_tests/assets/nodes/duplicate_node_ids.json
  • browser_tests/fixtures/helpers/NodeOperationsHelper.ts
  • browser_tests/tests/appModeBuilder.spec.ts
  • browser_tests/tests/nodeBadge.spec.ts
  • browser_tests/tests/nodeDisplay.spec.ts
  • browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts
  • browser_tests/tests/propertiesPanel/errorsTabModeAware.spec.ts
  • browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts
  • browser_tests/tests/vueNodes/nodeStates/registration.spec.ts
  • browser_tests/tests/vueNodes/widgets/legacy.spec.ts
  • browser_tests/tests/vueNodes/widgets/widgetReactivity.spec.ts
  • browser_tests/tests/workflowPersistence.spec.ts
  • docs/adr/0008-entity-component-system.md
  • docs/architecture/domain-glossary.md
  • docs/architecture/ecs-lifecycle-scenarios.md
  • docs/architecture/ecs-migration-plan.md
  • docs/architecture/ecs-target-architecture.md
  • docs/architecture/entity-interactions.md
  • docs/architecture/entity-problems.md
  • docs/architecture/link-topology-store.md
  • docs/architecture/node-badge-store.md
  • docs/architecture/node-data-store.md
  • docs/architecture/output-slot-connectivity.md
  • docs/architecture/proto-ecs-stores.md
  • docs/architecture/reroute-chain-store.md
  • src/components/builder/AppModeWidgetList.vue
  • src/components/graph/GraphCanvas.vue
  • src/components/graph/widgets/domWidgetZIndex.test.ts
  • src/components/rightSidePanel/errors/useErrorGroups.test.ts
  • src/components/rightSidePanel/errors/useErrorGroups.ts
  • src/components/rightSidePanel/parameters/SectionWidgets.vue
  • src/components/rightSidePanel/parameters/TabSubgraphInputs.test.ts
  • src/components/rightSidePanel/parameters/TabSubgraphInputs.vue
  • src/components/rightSidePanel/parameters/WidgetActions.test.ts
  • src/components/rightSidePanel/parameters/WidgetActions.vue
  • src/components/rightSidePanel/parameters/WidgetItem.test.ts
  • src/components/rightSidePanel/parameters/WidgetItem.vue
  • src/components/rightSidePanel/subgraph/SubgraphEditor.test.ts
  • src/components/rightSidePanel/subgraph/SubgraphEditor.vue
  • src/composables/graph/useErrorClearingHooks.test.ts
  • src/composables/graph/useErrorClearingHooks.ts
  • src/composables/graph/useGraphNodeManager.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/composables/graph/useNodeErrorFlagSync.test.ts
  • src/composables/graph/useVueNodeLifecycle.test.ts
  • src/composables/graph/useVueNodeLifecycle.ts
  • src/composables/node/useNodeBadge.ts
  • src/composables/node/useNodePricing.test.ts
  • src/composables/node/useNodePricing.ts
  • src/composables/node/usePriceBadge.test.ts
  • src/composables/node/usePriceBadge.ts
  • src/composables/useUpstreamValue.test.ts
  • src/composables/useUpstreamValue.ts
  • src/core/graph/subgraph/migration/proxyWidgetMigration.test.ts
  • src/core/graph/subgraph/migration/proxyWidgetMigration.ts
  • src/core/graph/subgraph/promotedInputWidget.ts
  • src/core/graph/subgraph/promotionUtils.test.ts
  • src/core/graph/subgraph/promotionUtils.ts
  • src/core/graph/subgraph/resolveConcretePromotedWidget.test.ts
  • src/core/graph/subgraph/resolveSubgraphInputLink.ts
  • src/core/graph/widgets/dynamicWidgets.test.ts
  • src/core/graph/widgets/dynamicWidgets.ts
  • src/core/graph/widgets/matchTypeConfiguring.test.ts
  • src/extensions/core/groupNode.ts
  • src/extensions/core/load3d.ts
  • src/extensions/core/rerouteNode.ts
  • src/extensions/core/saveImageExtraOutput.test.ts
  • src/extensions/core/widgetInputs.test.ts
  • src/extensions/core/widgetInputs.ts
  • src/extensions/core/widgetValuePropagation.test.ts
  • src/extensions/core/widgetValuePropagation.ts
  • src/lib/litegraph/src/LGraph.inputSlotRealign.test.ts
  • src/lib/litegraph/src/LGraph.serialise.test.ts
  • src/lib/litegraph/src/LGraph.test.ts
  • src/lib/litegraph/src/LGraph.ts
  • src/lib/litegraph/src/LGraphBadge.ts
  • src/lib/litegraph/src/LGraphCanvas.clipboard.test.ts
  • src/lib/litegraph/src/LGraphCanvas.cloneZIndex.test.ts
  • src/lib/litegraph/src/LGraphCanvas.drawConnections.test.ts
  • src/lib/litegraph/src/LGraphCanvas.ghost.test.ts
  • src/lib/litegraph/src/LGraphCanvas.groupSelection.test.ts
  • src/lib/litegraph/src/LGraphCanvas.slotHitDetection.test.ts
  • src/lib/litegraph/src/LGraphCanvas.ts
  • src/lib/litegraph/src/LGraphNode.nodeState.test.ts
  • src/lib/litegraph/src/LGraphNode.test.ts
  • src/lib/litegraph/src/LGraphNode.ts
  • src/lib/litegraph/src/LGraphNodeProperties.test.ts
  • src/lib/litegraph/src/LGraphNodeProperties.ts
  • src/lib/litegraph/src/LLink.store.test.ts
  • src/lib/litegraph/src/LLink.test.ts
  • src/lib/litegraph/src/LLink.ts
  • src/lib/litegraph/src/LiteGraphGlobal.ts
  • src/lib/litegraph/src/Reroute.store.test.ts
  • src/lib/litegraph/src/Reroute.ts
  • src/lib/litegraph/src/__fixtures__/nodeHelpers.ts
  • src/lib/litegraph/src/canvas/FloatingRenderLink.test.ts
  • src/lib/litegraph/src/canvas/FloatingRenderLink.ts
  • src/lib/litegraph/src/canvas/LinkConnector.core.test.ts
  • src/lib/litegraph/src/canvas/LinkConnector.integration.test.ts
  • src/lib/litegraph/src/canvas/LinkConnector.test.ts
  • src/lib/litegraph/src/canvas/LinkConnector.ts
  • src/lib/litegraph/src/canvas/LinkConnectorSubgraphInputValidation.test.ts
  • src/lib/litegraph/src/canvas/ToInputFromIoNodeLink.ts
  • src/lib/litegraph/src/canvas/ToInputRenderLink.ts
  • src/lib/litegraph/src/infrastructure/LGraphEventMap.ts
  • src/lib/litegraph/src/interfaces.ts
  • src/lib/litegraph/src/linkDeduplication.ts
  • src/lib/litegraph/src/litegraph.ts
  • src/lib/litegraph/src/node/NodeInputSlot.test.ts
  • src/lib/litegraph/src/node/NodeInputSlot.ts
  • src/lib/litegraph/src/node/NodeOutputSlot.test.ts
  • src/lib/litegraph/src/node/NodeOutputSlot.ts
  • src/lib/litegraph/src/node/NodeSlot.test.ts
  • src/lib/litegraph/src/node/NodeSlot.ts
  • src/lib/litegraph/src/node/SlotBase.ts
  • src/lib/litegraph/src/node/slotEcosystemPatterns.test.ts
  • src/lib/litegraph/src/node/slotLinks.test.ts
  • src/lib/litegraph/src/node/slotLinks.ts
  • src/lib/litegraph/src/node/slotUtils.test.ts
  • src/lib/litegraph/src/node/slotUtils.ts
  • src/lib/litegraph/src/nodeBadgeDraw.test.ts
  • src/lib/litegraph/src/nodeBadgeDraw.ts
  • src/lib/litegraph/src/serialization.test.ts
  • src/lib/litegraph/src/subgraph/ExecutableNodeDTO.test.ts
  • src/lib/litegraph/src/subgraph/ExecutableNodeDTO.ts
  • src/lib/litegraph/src/subgraph/SubgraphConversion.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphIO.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphInput.ts
  • src/lib/litegraph/src/subgraph/SubgraphInputNode.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts
  • src/lib/litegraph/src/subgraph/SubgraphOutput.ts
  • src/lib/litegraph/src/subgraph/SubgraphSerialization.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphSlotConnections.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphWidgetPromotion.test.ts
  • src/lib/litegraph/src/subgraph/__fixtures__/README.md
  • src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.test.ts
  • src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts
  • src/lib/litegraph/src/subgraph/subgraphDeduplication.test.ts
  • src/lib/litegraph/src/subgraph/subgraphDeduplication.ts
  • src/lib/litegraph/src/subgraph/subgraphUtils.test.ts
  • src/lib/litegraph/src/subgraph/subgraphUtils.ts
  • src/lib/litegraph/src/types/graphTriggers.ts
  • src/lib/litegraph/src/types/serialisation.ts
  • src/lib/litegraph/src/types/widgets.ts
  • src/lib/litegraph/src/utils/collections.test.ts
  • src/lib/litegraph/src/utils/collections.ts
  • src/lib/litegraph/src/utils/widget.ts
  • src/lib/litegraph/src/widgets/BaseWidget.test.ts
  • src/lib/litegraph/src/widgets/BaseWidget.ts
  • src/locales/ar/main.json
  • src/locales/en/main.json
  • src/locales/es/main.json
  • src/locales/fa/main.json
  • src/locales/fr/main.json
  • src/locales/he/main.json
  • src/locales/ja/main.json
  • src/locales/ko/main.json
  • src/locales/pt-BR/main.json
  • src/locales/ru/main.json
  • src/locales/tr/main.json
  • src/locales/zh-TW/main.json
  • src/locales/zh/main.json
  • src/platform/cloud/subscription/composables/useFreeTierQuota.ts
  • src/platform/missingMedia/missingMediaStore.ts
  • src/platform/missingModel/missingModelScan.test.ts
  • src/platform/missingModel/missingModelScan.ts
  • src/platform/missingModel/missingModelStore.ts
  • src/platform/nodeReplacement/useNodeReplacement.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.ts
  • src/platform/telemetry/nodeAdded/installNodeAddedTelemetry.test.ts
  • src/platform/telemetry/nodeAdded/installNodeAddedTelemetry.ts
  • src/platform/workflow/core/services/workflowService.insertWorkflow.test.ts
  • src/platform/workflow/core/services/workflowService.ts
  • src/renderer/core/canvas/canvasStore.test.ts
  • src/renderer/core/canvas/canvasStore.ts
  • src/renderer/core/canvas/links/linkConnectorAdapter.ts
  • src/renderer/core/layout/operations/layoutMutations.test.ts
  • src/renderer/core/layout/operations/layoutMutations.ts
  • src/renderer/core/layout/store/layoutStore.test.ts
  • src/renderer/core/layout/store/layoutStore.ts
  • src/renderer/core/layout/types.ts
  • src/renderer/extensions/linearMode/PartnerNodesList.vue
  • src/renderer/extensions/minimap/composables/useMinimap.test.ts
  • src/renderer/extensions/minimap/composables/useMinimapGraph.test.ts
  • src/renderer/extensions/minimap/composables/useMinimapGraph.ts
  • src/renderer/extensions/minimap/data/AbstractMinimapDataSource.ts
  • src/renderer/extensions/minimap/data/LayoutStoreDataSource.ts
  • src/renderer/extensions/minimap/data/MinimapDataSource.test.ts
  • src/renderer/extensions/minimap/data/MinimapDataSourceFactory.ts
  • src/renderer/extensions/minimap/minimapCanvasRenderer.test.ts
  • src/renderer/extensions/vueNodes/components/LGraphNode.subgraph.test.ts
  • src/renderer/extensions/vueNodes/components/LGraphNode.test.ts
  • src/renderer/extensions/vueNodes/components/LGraphNode.vue
  • src/renderer/extensions/vueNodes/components/LGraphNodePreview.test.ts
  • src/renderer/extensions/vueNodes/components/LGraphNodePreview.vue
  • src/renderer/extensions/vueNodes/components/NodeContent.vue
  • src/renderer/extensions/vueNodes/components/NodeHeader.test.ts
  • src/renderer/extensions/vueNodes/components/NodeHeader.vue
  • src/renderer/extensions/vueNodes/components/NodeSlots.test.ts
  • src/renderer/extensions/vueNodes/components/NodeSlots.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/components/WidgetGrid.vue
  • src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts
  • src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts
  • src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts
  • src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts
  • src/renderer/extensions/vueNodes/composables/useNodeTooltips.test.ts
  • src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts
  • src/renderer/extensions/vueNodes/composables/usePartitionedBadges.test.ts
  • src/renderer/extensions/vueNodes/composables/usePartitionedBadges.ts
  • src/renderer/extensions/vueNodes/composables/useProcessedWidgets.test.ts
  • src/renderer/extensions/vueNodes/composables/useProcessedWidgets.ts
  • src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.autoPan.test.ts
  • src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts
  • src/renderer/extensions/vueNodes/layout/nodeSizeReflow.test.ts
  • src/renderer/extensions/vueNodes/types/widgetGrid.ts
  • src/renderer/extensions/vueNodes/utils/__tests__/nodeDataUtils.test.ts
  • src/renderer/extensions/vueNodes/utils/nodeDataUtils.ts
  • src/renderer/extensions/vueNodes/utils/nodeErrorState.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetButton.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetDOM.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetDOM.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useProgressTextWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/renderer/glsl/glslPreviewUtils.ts
  • src/renderer/glsl/useGLSLUniforms.test.ts
  • src/renderer/glsl/useGLSLUniforms.ts
  • src/scripts/app.test.ts
  • src/scripts/app.ts
  • src/scripts/promotedWidgetControl.test.ts
  • src/scripts/promotedWidgetControl.ts
  • src/scripts/widgets.ts
  • src/services/litegraphService.ts
  • src/stores/appModeStore.test.ts
  • src/stores/linkStore.test.ts
  • src/stores/linkStore.ts
  • src/stores/nodeDataStore.test.ts
  • src/stores/nodeDataStore.ts
  • src/stores/rerouteStore.test.ts
  • src/stores/rerouteStore.ts
  • src/stores/widgetValueStore.graphReactivity.test.ts
  • src/stores/widgetValueStore.test.ts
  • src/stores/widgetValueStore.ts
  • src/stores/workspace/favoritedWidgetsStore.ts
  • src/systems/badgeSystem.pricing.test.ts
  • src/systems/badgeSystem.subgraph.test.ts
  • src/systems/badgeSystem.test.ts
  • src/systems/badgeSystem.ts
  • src/types/badgeData.ts
  • src/types/linkTopology.ts
  • src/types/nodeState.ts
  • src/types/rerouteChain.ts
  • src/types/simplifiedWidget.ts
  • src/types/widgetState.ts
  • src/utils/__tests__/litegraphTestUtils.ts
  • src/utils/graphTraversalUtil.test.ts
  • src/utils/graphTraversalUtil.ts
  • src/utils/linkFixer.test.ts
  • src/utils/linkFixer.ts
  • src/utils/litegraphUtil.test.ts
  • src/utils/litegraphUtil.ts
  • src/utils/searchAndReplace.test.ts
  • src/utils/widgetUtil.ts
  • tools/devtools/web/legacyWidget.js
💤 Files with no reviewable changes (27)
  • src/lib/litegraph/src/LGraphNodeProperties.test.ts
  • src/composables/node/usePriceBadge.test.ts
  • src/composables/node/usePriceBadge.ts
  • src/lib/litegraph/src/LGraphNodeProperties.ts
  • src/lib/litegraph/src/LGraphBadge.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/composables/graph/useGraphNodeManager.test.ts
  • browser_tests/tests/nodeBadge.spec.ts
  • src/locales/ru/main.json
  • src/lib/litegraph/src/canvas/ToInputRenderLink.ts
  • src/locales/ar/main.json
  • src/locales/ja/main.json
  • src/locales/zh/main.json
  • src/lib/litegraph/src/types/graphTriggers.ts
  • src/lib/litegraph/src/node/SlotBase.ts
  • src/locales/zh-TW/main.json
  • src/locales/tr/main.json
  • src/lib/litegraph/src/canvas/ToInputFromIoNodeLink.ts
  • src/locales/es/main.json
  • src/locales/fr/main.json
  • src/locales/fa/main.json
  • src/scripts/app.ts
  • browser_tests/fixtures/helpers/NodeOperationsHelper.ts
  • src/renderer/core/layout/types.ts
  • src/locales/ko/main.json
  • src/locales/pt-BR/main.json
  • src/locales/he/main.json

Comment on lines +1360 to +1365
const linkId = graph.nodes.find((n) => n.title === 'Preview Image')
?.inputs[0].link
if (!linkId) return 'failed to resolve link id'

const rerouteId = graph.getLink(linkId)?.parentId
if (!rerouteId) return 'failed to resolve reroute id'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Stop reading the removed input-link mirror.

input.link is a legacy mirror removed by this migration, so this poll cannot resolve the link. Find the Preview Image link through the graph topology API/store (or graph.links) before checking its parentId.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts`
around lines 1360 - 1365, Update the link-resolution logic near the Preview
Image node to stop reading the removed inputs[0].link mirror. Resolve the node’s
connected link through the graph topology API or graph.links, then use that
link’s identifier with getLink to obtain parentId while preserving the existing
failure messages.

Comment thread docs/architecture/entity-interactions.md
Comment thread docs/architecture/link-topology-store.md Outdated
Comment on lines +89 to 110
const mappedSelections = computed((): WidgetEntry[] => {
return resolvedInputs.value.flatMap((entry) => {
if (entry.status !== 'resolved') return []
const { widgetId, node, widget, config } = entry
if (node.mode !== LGraphEventMode.ALWAYS) return []

if (!nodeDataByNode.has(node)) {
nodeDataByNode.set(node, nodeToNodeData(node))
}
const fullNodeData = nodeDataByNode.get(node)!

const matchingWidget = fullNodeData.widgets?.find((vueWidget) => {
if (vueWidget.slotMetadata?.linked) return false
return vueWidget.widgetId === widgetId
})
if (!matchingWidget) return []

matchingWidget.slotMetadata = undefined
matchingWidget.nodeId = node.id
ensureSelectedWidgetState(widgetId, widget)
const fullNodeData = nodeToNodeData(node, widgetId)
if (isWidgetInputLinked(node, widget.name)) return []

return [
{
key: widgetId,
persistedHeight: config?.height,
description: config?.description,
nodeData: {
...fullNodeData,
widgets: [matchingWidget]
},
nodeData: fullNodeData,
widgetIds: [widgetId],
action: { widget, node }
}
]
})
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

mappedSelections mutates store state while deriving.

ensureSelectedWidgetState writes into widgetValueStore from inside a computed getter that also reads widgetValueStore.getWidget(...). Registering a widget invalidates the map this getter just tracked, so the computed re-runs on its own write (it only converges because the second pass short-circuits). Registration belongs in a watchEffect/watch on resolvedInputs, leaving the computed pure.

Also, nodeToNodeData(node, widgetId) (which builds a drop indicator and an image URL) is evaluated before the isWidgetInputLinked early return, so it is wasted for every filtered-out row.

♻️ Proposed restructure
-const mappedSelections = computed((): WidgetEntry[] => {
+watchEffect(() => {
+  for (const entry of resolvedInputs.value) {
+    if (entry.status !== 'resolved') continue
+    ensureSelectedWidgetState(entry.widgetId, entry.widget)
+  }
+})
+
+const mappedSelections = computed((): WidgetEntry[] => {
   return resolvedInputs.value.flatMap((entry) => {
     if (entry.status !== 'resolved') return []
     const { widgetId, node, widget, config } = entry
     if (node.mode !== LGraphEventMode.ALWAYS) return []
-
-    ensureSelectedWidgetState(widgetId, widget)
-    const fullNodeData = nodeToNodeData(node, widgetId)
     if (isWidgetInputLinked(node, widget.name)) return []
 
     return [
       {
         key: widgetId,
         persistedHeight: config?.height,
         description: config?.description,
-        nodeData: fullNodeData,
+        nodeData: nodeToNodeData(node, widgetId),
         widgetIds: [widgetId],
         action: { widget, node }
       }
     ]
   })
 })
📝 Committable suggestion

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

Suggested change
const mappedSelections = computed((): WidgetEntry[] => {
return resolvedInputs.value.flatMap((entry) => {
if (entry.status !== 'resolved') return []
const { widgetId, node, widget, config } = entry
if (node.mode !== LGraphEventMode.ALWAYS) return []
if (!nodeDataByNode.has(node)) {
nodeDataByNode.set(node, nodeToNodeData(node))
}
const fullNodeData = nodeDataByNode.get(node)!
const matchingWidget = fullNodeData.widgets?.find((vueWidget) => {
if (vueWidget.slotMetadata?.linked) return false
return vueWidget.widgetId === widgetId
})
if (!matchingWidget) return []
matchingWidget.slotMetadata = undefined
matchingWidget.nodeId = node.id
ensureSelectedWidgetState(widgetId, widget)
const fullNodeData = nodeToNodeData(node, widgetId)
if (isWidgetInputLinked(node, widget.name)) return []
return [
{
key: widgetId,
persistedHeight: config?.height,
description: config?.description,
nodeData: {
...fullNodeData,
widgets: [matchingWidget]
},
nodeData: fullNodeData,
widgetIds: [widgetId],
action: { widget, node }
}
]
})
})
watchEffect(() => {
for (const entry of resolvedInputs.value) {
if (entry.status !== 'resolved') continue
ensureSelectedWidgetState(entry.widgetId, entry.widget)
}
})
const mappedSelections = computed((): WidgetEntry[] => {
return resolvedInputs.value.flatMap((entry) => {
if (entry.status !== 'resolved') return []
const { widgetId, node, widget, config } = entry
if (node.mode !== LGraphEventMode.ALWAYS) return []
if (isWidgetInputLinked(node, widget.name)) return []
return [
{
key: widgetId,
persistedHeight: config?.height,
description: config?.description,
nodeData: nodeToNodeData(node, widgetId),
widgetIds: [widgetId],
action: { widget, node }
}
]
})
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/builder/AppModeWidgetList.vue` around lines 89 - 110, Make
mappedSelections a pure computed by removing ensureSelectedWidgetState from its
getter and register resolved widgets in a watch/watchEffect driven by
resolvedInputs instead. In the computed, check isWidgetInputLinked before
calling nodeToNodeData so filtered linked inputs do not build unused data;
preserve the existing selection mapping for unlinked ALWAYS-mode entries.

Comment on lines +32 to +34
vi.spyOn(app, 'rootGraph', 'get').mockReturnValue(graph)
vi.spyOn(app, 'isGraphReady', 'get').mockReturnValue(true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Spies on app.rootGraph/app.isGraphReady are never restored between tests.

setupGraphWithStore() (and the two subgraph tests) call vi.spyOn(app, 'rootGraph', 'get')/vi.spyOn(app, 'isGraphReady', 'get') repeatedly across 7 it() blocks, but beforeEach only calls setActivePinia(...) — there's no vi.restoreAllMocks()/vi.resetAllMocks(). Each new vi.spyOn wraps the previous spy instead of replacing it cleanly, which is harmless today but is exactly the pattern the testing guidance calls out.

♻️ Suggested fix
   beforeEach(() => {
     setActivePinia(createTestingPinia({ stubActions: false }))
   })
+
+  afterEach(() => {
+    vi.restoreAllMocks()
+  })

Based on learnings, "Mocking: keep vi.mock() module mocks contained; reset with vi.resetAllMocks() in beforeEach" (docs/testing/vitest-patterns.md, required context for this test file).

Also applies to: 127-129, 208-210

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/composables/graph/useNodeErrorFlagSync.test.ts` around lines 32 - 34,
Restore Vitest mocks before each test in the setup for this suite, alongside
setActivePinia, so the rootGraph and isGraphReady getter spies created by
setupGraphWithStore and the subgraph tests do not accumulate across it blocks.
Use the project’s required reset/restore pattern consistently for all tests.

Source: Path instructions

Comment on lines +146 to +151
function getWidgetRenderState(
widgetId: WidgetId
): WidgetRenderState | undefined {
const { graphId } = parseWidgetId(widgetId)
return getGraphWidgetRenderStates(graphId).get(widgetId)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

getWidgetRenderState lacks the isWidgetId guard its sibling has, so a bad id throws.

getWidget returns undefined for a non-conforming id, but getWidgetRenderState goes straight to parseWidgetId, which throws Invalid widget id. It's called per-widget from useProcessedWidgets.processWidget inside a computed, so one malformed id from an externally supplied widgetIds list takes down the whole node's widget rendering rather than skipping a row.

🛡️ Proposed fix
   function getWidgetRenderState(
     widgetId: WidgetId
   ): WidgetRenderState | undefined {
+    if (!isWidgetId(widgetId)) return undefined
+
     const { graphId } = parseWidgetId(widgetId)
     return getGraphWidgetRenderStates(graphId).get(widgetId)
   }
📝 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
function getWidgetRenderState(
widgetId: WidgetId
): WidgetRenderState | undefined {
const { graphId } = parseWidgetId(widgetId)
return getGraphWidgetRenderStates(graphId).get(widgetId)
}
function getWidgetRenderState(
widgetId: WidgetId
): WidgetRenderState | undefined {
if (!isWidgetId(widgetId)) return undefined
const { graphId } = parseWidgetId(widgetId)
return getGraphWidgetRenderStates(graphId).get(widgetId)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stores/widgetValueStore.ts` around lines 146 - 151, Update
getWidgetRenderState to validate widgetId with the existing isWidgetId guard
before calling parseWidgetId; return undefined for non-conforming IDs, matching
getWidget’s behavior, while preserving the current graph-state lookup for valid
IDs.

Comment thread src/stores/workspace/favoritedWidgetsStore.ts
Comment thread src/systems/badgeSystem.pricing.test.ts Outdated
Comment on lines +167 to +187
function gatherSubgraphCredits(wrapper: SubgraphNode): PricingBadgeSources {
const pricing = useNodePricing()
const apiLeaves = mapUniqueNodes(wrapper.subgraph, (node) =>
!node.isSubgraphNode() && node.constructor?.nodeData?.api_node
? node
: undefined
)
for (const leaf of apiLeaves) {
void pricing.getNodeRevisionRef(leaf.id).value
}

if (apiLeaves.length !== 1) {
return { kind: 'subgraph', apiNodeCount: apiLeaves.length, singleLabel: '' }
}
const leaf = apiLeaves[0]
const singleLabel = pricing.getNodeDisplayPrice(
leaf,
collectPromotedOverrides(wrapper, leaf)
)
return { kind: 'subgraph', apiNodeCount: 1, singleLabel }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wrapper credits don't track the inner node's dynamic pricing sources.

The non-subgraph path calls touchPricingSources(graphId, node) (Line 193), which registers dependencies on priced widget values and priced input connections. gatherSubgraphCredits only touches getNodeRevisionRef(leaf.id), and collectPromotedOverrides only reads promoted widget values. An inner api node priced on a non-promoted widget or on an inner input connection will therefore keep a stale wrapper badge, since the revision ref only bumps after an evaluation that this computed is itself gating.

🐛 Proposed fix for the single-leaf case
   const leaf = apiLeaves[0]
+  const graphId = wrapper.graph?.rootGraph.id
+  if (graphId !== undefined) touchPricingSources(graphId, leaf)
   const singleLabel = pricing.getNodeDisplayPrice(
     leaf,
     collectPromotedOverrides(wrapper, leaf)
   )

Note the inner node lives in wrapper.subgraph; use whichever graph id keys the link/widget stores for inner nodes.

📝 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
function gatherSubgraphCredits(wrapper: SubgraphNode): PricingBadgeSources {
const pricing = useNodePricing()
const apiLeaves = mapUniqueNodes(wrapper.subgraph, (node) =>
!node.isSubgraphNode() && node.constructor?.nodeData?.api_node
? node
: undefined
)
for (const leaf of apiLeaves) {
void pricing.getNodeRevisionRef(leaf.id).value
}
if (apiLeaves.length !== 1) {
return { kind: 'subgraph', apiNodeCount: apiLeaves.length, singleLabel: '' }
}
const leaf = apiLeaves[0]
const singleLabel = pricing.getNodeDisplayPrice(
leaf,
collectPromotedOverrides(wrapper, leaf)
)
return { kind: 'subgraph', apiNodeCount: 1, singleLabel }
}
function gatherSubgraphCredits(wrapper: SubgraphNode): PricingBadgeSources {
const pricing = useNodePricing()
const apiLeaves = mapUniqueNodes(wrapper.subgraph, (node) =>
!node.isSubgraphNode() && node.constructor?.nodeData?.api_node
? node
: undefined
)
for (const leaf of apiLeaves) {
void pricing.getNodeRevisionRef(leaf.id).value
}
if (apiLeaves.length !== 1) {
return { kind: 'subgraph', apiNodeCount: apiLeaves.length, singleLabel: '' }
}
const leaf = apiLeaves[0]
const graphId = wrapper.graph?.rootGraph.id
if (graphId !== undefined) touchPricingSources(graphId, leaf)
const singleLabel = pricing.getNodeDisplayPrice(
leaf,
collectPromotedOverrides(wrapper, leaf)
)
return { kind: 'subgraph', apiNodeCount: 1, singleLabel }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/systems/badgeSystem.ts` around lines 167 - 187, Update
gatherSubgraphCredits to touch all dynamic pricing dependencies for the single
inner API leaf, not only pricing.getNodeRevisionRef(leaf.id). Reuse the
dependency-registration behavior from touchPricingSources, passing the
appropriate inner graph-id keys from wrapper.subgraph for priced widget values
and input connections, while preserving the existing multi-leaf and
display-price behavior.

Comment thread src/systems/badgeSystem.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review continued from previous batch...

Comment thread docs/architecture/node-data-store.md Outdated
Comment on lines +28 to +43
```
NodeState {
id: NodeId
graphId: UUID // owning (sub)graph — partitioning + locator ids
type: string // identity
title: string
titleMode?: TitleMode
mode: LGraphEventMode
flags: { collapsed?, pinned?, ghost? }
color?: string
bgcolor?: string
shape?: RenderShape
resizable?: boolean
showAdvanced?: boolean
}
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language to the NodeState code fence.

Use ts so Markdown linting passes and the schema is highlighted correctly.

🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 28-28: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/node-data-store.md` around lines 28 - 43, Add the
TypeScript language identifier to the NodeState fenced code block in the
architecture documentation, changing the opening fence to use ts while leaving
the schema content unchanged.

Source: Linters/SAST tools

Comment thread docs/architecture/proto-ecs-stores.md Outdated
Comment thread src/lib/litegraph/src/__fixtures__/nodeHelpers.ts
Comment thread src/lib/litegraph/src/LGraph.inputSlotRealign.test.ts Outdated
Comment thread src/lib/litegraph/src/linkDeduplication.ts Outdated
Comment thread src/lib/litegraph/src/node/slotUtils.test.ts
Comment thread src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts Outdated
Comment thread src/lib/litegraph/src/subgraph/subgraphDeduplication.ts
import { createTestingPinia } from '@pinia/testing'
import { setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it } from 'vitest'
import { beforeEach, describe, expect, it, vi } from 'vitest'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Restore the console spy unconditionally.

An assertion failure skips mockRestore(), leaving console.error mocked for
later tests. Use afterEach(() => vi.restoreAllMocks()) or a try/finally.

Also applies to: 35-50

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/litegraph/src/subgraph/subgraphUtils.test.ts` at line 3, Update the
tests in subgraphUtils.test.ts to restore mocked globals after every test, using
an afterEach hook with vi.restoreAllMocks(). Ensure console.error is restored
even when assertions fail, and remove any reliance on cleanup that only runs
after successful assertions.

Source: Path instructions

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 49

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/components/NodeSlots.vue (1)

10-19: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Collapse the per-slot lookups into one computed row list.

getActualInputIndex now runs three times per input during render (:key, :index, and inside :connected), each a linear findIndex over nodeData.inputs — O(N²) per node render on the canvas hot path. Building the rows once also removes the duplicated rootGraphId guards in isInputConnected/isOutputConnected.

♻️ Sketch
const inputRows = computed(() => {
  const graphId = canvasStore.rootGraphId
  return filteredInputs.value.map((input, filteredIndex) => {
    const actualIndex = nodeData.inputs.indexOf(input)
    const index = actualIndex !== -1 ? actualIndex : filteredIndex
    return {
      input,
      index,
      connected: graphId
        ? linkStore.isInputSlotConnected(graphId, nodeData.id, index)
        : false
    }
  })
})
-      <InputSlot
-        v-for="(input, index) in filteredInputs"
-        :key="`input-${input.name}-${getActualInputIndex(input, index)}`"
-        :slot-data="input"
-        :node-type="nodeData?.type || ''"
-        :node-id="nodeData.id"
-        :has-error="inputHasError(input)"
-        :index="getActualInputIndex(input, index)"
-        :connected="isInputConnected(getActualInputIndex(input, index))"
-      />
+      <InputSlot
+        v-for="row in inputRows"
+        :key="`input-${row.input.name}-${row.index}`"
+        :slot-data="row.input"
+        :node-type="nodeData.type"
+        :node-id="nodeData.id"
+        :has-error="inputHasError(row.input)"
+        :index="row.index"
+        :connected="row.connected"
+      />

Also applies to: 119-125

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/extensions/vueNodes/components/NodeSlots.vue` around lines 10 -
19, In NodeSlots.vue, add computed inputRows and outputRows that resolve each
slot’s actual index once and calculate connection state using the rootGraphId
within the computed mapping. Update the InputSlot and OutputSlot v-for blocks to
consume these row objects for keys, slot data, indices, and connected state,
removing repeated getActualInputIndex calls and redundant rootGraphId guards
from isInputConnected/isOutputConnected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts`:
- Around line 1360-1365: Update the link-resolution logic near the Preview Image
node to stop reading the removed inputs[0].link mirror. Resolve the node’s
connected link through the graph topology API or graph.links, then use that
link’s identifier with getLink to obtain parentId while preserving the existing
failure messages.

In `@docs/architecture/entity-interactions.md`:
- Around line 106-108: Update the Legend in entity-interactions.md to define the
-.- edge style used by the derived linkIds and floatingLinkIds relationships,
including its meaning as a dotted, non-directional derived relationship. Keep
the existing owns, references, and extends legend entries unchanged.

In `@docs/architecture/link-topology-store.md`:
- Around line 18-21: Add language annotations to both new fenced code blocks:
use an appropriate tag such as ts or text for the LinkTopology shape in
docs/architecture/link-topology-store.md lines 18-21, and ts for the BadgeData
union in docs/architecture/node-badge-store.md lines 40-44, resolving
markdownlint MD040 without changing the documented content.

In `@docs/architecture/node-data-store.md`:
- Around line 28-43: Add the TypeScript language identifier to the NodeState
fenced code block in the architecture documentation, changing the opening fence
to use ts while leaving the schema content unchanged.

In `@docs/architecture/proto-ecs-stores.md`:
- Around line 388-413: Update the migration matrix and priority entries to
reflect the implemented state: add NodeDataStore-extracted data to the Node row,
remove stale input.link/output.links mirror work from the Link and Slot gaps,
and adjust the related priority descriptions accordingly. Use the existing
NodeDataStore and output-slot-connectivity documentation as the source of truth
while preserving the remaining unimplemented extraction work.

In `@src/components/builder/AppModeWidgetList.vue`:
- Around line 89-110: Make mappedSelections a pure computed by removing
ensureSelectedWidgetState from its getter and register resolved widgets in a
watch/watchEffect driven by resolvedInputs instead. In the computed, check
isWidgetInputLinked before calling nodeToNodeData so filtered linked inputs do
not build unused data; preserve the existing selection mapping for unlinked
ALWAYS-mode entries.

In `@src/composables/graph/useNodeErrorFlagSync.test.ts`:
- Around line 32-34: Restore Vitest mocks before each test in the setup for this
suite, alongside setActivePinia, so the rootGraph and isGraphReady getter spies
created by setupGraphWithStore and the subgraph tests do not accumulate across
it blocks. Use the project’s required reset/restore pattern consistently for all
tests.

In `@src/composables/graph/useVueNodeLifecycle.ts`:
- Around line 94-99: Update disposeVueNodeLayout to clear all node entries from
layoutStore after stopping synchronization and listeners, undoing the data
seeded by initializeVueNodeLayout. Ensure the shouldRenderVueNodes transition
handled by the whenever block leaves layoutStore empty when Vue rendering is
disabled, preserving the existing cleanup state resets.

In `@src/composables/node/useNodePricing.ts`:
- Around line 589-593: The cache-miss fallback in the node pricing evaluation
flow must use the previous label for the current signature rather than the
newest label from any cached signature. Update the state associated with
scheduleEvaluation or the surrounding caller to track and return the last label
per signature/read context, while preserving the empty-string fallback when no
matching prior label exists.

In `@src/core/graph/subgraph/promotionUtils.ts`:
- Around line 298-308: Replace the direct promotedState.label mutation in the
hostInput promotion block with the widget store’s dedicated label-update action,
adding that action if none exists. Pass hostInput.widgetId and sourceSlot.label
through the store API so state invariants, telemetry, and persistence hooks are
preserved.

In `@src/core/graph/widgets/dynamicWidgets.test.ts`:
- Around line 10-11: Remove the redundant module-level
setActivePinia(createTestingPinia({ stubActions: false })) call in
dynamicWidgets.test.ts, keeping the beforeEach setup as the sole initialization
so each test receives a fresh testing Pinia.

In `@src/core/graph/widgets/dynamicWidgets.ts`:
- Around line 269-276: Replace the deprecated indexed link lookup in the
topology loop with the graph’s store-consistent getLink() method, using
topology.id as the lookup key and preserving the existing missing-link continue
behavior.
- Around line 555-563: Move the syncNodeWidgetOrder(node) call out of the
toRemove iteration and invoke it once after the loop completes. Preserve the
existing widget removal and cleanup behavior, including skipping inputs without
a widget name.

In `@src/extensions/core/widgetInputs.test.ts`:
- Around line 19-37: Remove the explicit 30_000 ms timeout argument from the
synchronous test defined by “resets itself when the store reports a link the
graph cannot resolve,” leaving the test body and its assertions unchanged so
Vitest uses its default timeout.

In `@src/extensions/core/widgetValuePropagation.ts`:
- Around line 25-28: Update the early-return guard in the widget value
propagation function to account for both store links and extraLinks, allowing
propagation when extraLinks is non-empty even if linked is empty. Keep returning
early only when neither source contains endpoints, and preserve the existing
endpoint-merging behavior.

In `@src/lib/litegraph/src/__fixtures__/nodeHelpers.ts`:
- Around line 38-55: Update createTestWidgetNode to register an onTestFinished
cleanup alongside its WIDGET_NODE_TYPE registration, restoring the previous
LiteGraph.registered_node_types entry or removing the test type after the test
completes, matching createTestNode’s cleanup pattern. Keep node creation and
graph.add behavior unchanged.

In `@src/lib/litegraph/src/LGraph.inputSlotRealign.test.ts`:
- Around line 319-322: Update the parameter list for the parameterized test
“rekeys a serialized %s” to destructure each it.for tuple into its name and
usePurgedAlias values, ensuring both registered-link and purged-alias cases
execute with their intended boolean.

In `@src/lib/litegraph/src/LGraphCanvas.ts`:
- Line 5706: Update the clip-path shape selection near drawNodeShape to use
node.renderingShape instead of node.shape or a direct BOX fallback, keeping
clipping consistent with the shape resolved by the node body and its
class/default fallbacks.

In `@src/lib/litegraph/src/LGraphNode.test.ts`:
- Around line 878-899: Remove the redundant “defaults to a normal title” test
because it only checks constructor initialization. In the remaining “titleMode
in node state” test, avoid the private _state shape by asserting the NO_TITLE
value through the store-held state returned by getGraphNodesFor(...) or the
public title_mode accessor, while preserving coverage that TitlelessNode’s
static title_mode is propagated.

In `@src/lib/litegraph/src/LGraphNode.ts`:
- Around line 347-349: Make the _state accessors consistent by routing the id
setter, flags setter, and resizable setter through setTrackedState, ensuring
each emits node:property:changed like title, mode, color, bgcolor, and
showAdvanced. Update the affected setters and preserve their existing values and
behavior, including resizable changes triggered by pin() and configure().
- Around line 4023-4031: Optimize updateComputedDisabled to avoid repeated
inputs scans during rendering: update getSlotFromWidget or the surrounding
widget loop to return/reuse the matching input index and determine connectivity
directly from that result, rather than calling this.inputs.indexOf(slot) after
the lookup. Preserve the existing computedDisabled behavior for widgets without
slots, disabled widgets, and connected inputs.
- Around line 1149-1157: The output serialization mapping in LGraphNode
serialization currently masks a type mismatch with `@ts-expect-error`. Remove that
directive and reconcile the return type of outputAsSerialisable with the
ISserialisedNode outputs element type so the mapping type-checks without
suppression.
- Around line 1062-1070: Update the input restoration loop in configure() to
invoke onConnectionsChange only when the serialized input contains a link that
was successfully restored; skip the callback for bare inputs, matching the
output restoration behavior. Continue invoking onInputAdded for every input.
- Around line 1884-1911: Update removeInput so the replaceNodeInputs failure
path, identified when this.inputs still includes slotInfo, logs a warning before
returning. Keep the existing removal callback, floating-link adjustment, and
canvas-dirty behavior unchanged for successful removals.

In `@src/lib/litegraph/src/linkDeduplication.ts`:
- Around line 54-72: Remove the unused link lookup and its guard from
purgeOrphanedLinks; iterate over non-survivor ids and call graph._removeLink(id)
directly, preserving the survivor re-registration logic afterward.

In `@src/lib/litegraph/src/LLink.ts`:
- Around line 98-112: Update applyEndpointPatch to propagate failures from
useLinkStore().updateEndpoint instead of only logging result.error and returning
normally. Throw or otherwise surface the rejected patch through the existing
endpoint setter/configure call chain, while preserving direct Object.assign
behavior for unregistered links.

In `@src/lib/litegraph/src/node/slotLinks.ts`:
- Around line 156-158: Collapse the short single-statement if block in the
finalInputs validation by placing the condition and throw on one line,
preserving the existing duplicate-slot check and error message.

In `@src/lib/litegraph/src/node/slotUtils.test.ts`:
- Around line 32-43: Update the ordering assertion in the “serialises the links
leaving the slot, ascending by id” test to sort LinkId values with an explicit
numeric comparator. Keep the existing length assertion and verify
serialised.links against its numerically ascending copy.

In `@src/lib/litegraph/src/nodeBadgeDraw.ts`:
- Around line 76-86: The registerBadgeRowsProvider function permanently pins the
first provider and offers no cleanup path. Add an unregister/disposer mechanism
tied to the registered provider, or expose a reset API for HMR and tests, while
preserving rejection of conflicting active registrations.
- Around line 52-62: Update the badge construction loop to create the credits
icon only when row.kind is exactly 'credits', rather than treating every
non-core row as credits. Preserve core-row skipping and ensure other BadgeData
kinds are handled explicitly or rejected by the type system instead of silently
receiving the credits icon.

In `@src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts`:
- Around line 68-95: Update enableSubgraphNodeCreation to keep the event
listener and type-to-constructor registrations local to each helper instance.
Make the returned disposer remove its own listener and delete only registrations
whose current constructor still matches the locally stored constructor,
preventing helpers from affecting each other; add coverage that creates another
subgraph after disposal and verifies no registration occurs.

In `@src/lib/litegraph/src/subgraph/subgraphDeduplication.ts`:
- Around line 114-129: Parameterize findNextAvailableId with an identifier-space
label and use it in the exhaustion error instead of the hardcoded node-specific
message. Update each caller, including node and reroute ID allocation, to
provide the appropriate label while preserving the existing allocation behavior.

In `@src/lib/litegraph/src/subgraph/subgraphUtils.test.ts`:
- Line 3: Update the tests in subgraphUtils.test.ts to restore mocked globals
after every test, using an afterEach hook with vi.restoreAllMocks(). Ensure
console.error is restored even when assertions fail, and remove any reliance on
cleanup that only runs after successful assertions.

In `@src/platform/cloud/subscription/composables/useFreeTierQuota.ts`:
- Around line 28-31: Update hasInvalidNodes in useFreeTierQuota to derive the
root graph from useCanvasStore().currentGraph instead of the non-reactive
app.graph, preserving the existing graphCreditsBadges check and false fallback.
Ensure the composable accesses the reactive currentGraph source so
freeTierExecutionPermitted recalculates after workflow switches.

In `@src/renderer/extensions/minimap/data/MinimapDataSource.test.ts`:
- Around line 48-59: Update registerNodeState so useNodeDataStore().registerNode
receives the graphId parameter as its store partition key instead of the
hardcoded GRAPH_ID, while continuing to set the same graphId on the created node
state.

In `@src/renderer/extensions/vueNodes/components/LGraphNode.vue`:
- Around line 702-711: Update the widgetIds computed in LGraphNode.vue to derive
graph identity from the reactive canvasStore.rootGraphId instead of
app.rootGraph?.id. Preserve the existing empty-result guard and getNodeWidgetIds
lookup, ensuring hasRenderableWidgets and downstream computeds refresh when the
root graph is replaced.

In `@src/renderer/extensions/vueNodes/components/NodeHeader.test.ts`:
- Around line 22-32: Replace the local makeNodeData fixture with the shared
createNodeState fixture from litegraphTestUtils, adding the required import and
passing the existing overrides through it. Remove the hand-rolled NodeState
defaults so shared fields, including the ALWAYS mode, remain consistent.

In `@src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts`:
- Around line 265-267: Replace the Tailwind class assertion in the NodeWidgets
test with an assertion on a semantic error signal exposed by the widget row,
such as data-has-error or aria-invalid. Update the relevant widget-row rendering
symbol to provide that signal if needed, while preserving the existing error
behavior.

In `@src/renderer/extensions/vueNodes/composables/useProcessedWidgets.test.ts`:
- Around line 342-351: Update the test around processWidgets to avoid asserting
the literal Tailwind value ring ring-component-node-widget-advanced. Verify the
advanced/non-advanced borderStyle behavior by comparing results for showAdvanced
or reuse a shared source constant, and apply the same change to the duplicate
assertion near the other advanced-widget test.

In `@src/renderer/extensions/vueNodes/composables/useProcessedWidgets.ts`:
- Around line 411-414: Update the valueTooltip logic in useProcessedWidgets so
object-valued asset/combo widgets do not produce a tooltip from String(value),
particularly avoiding “[object Object]”. Only attach the tooltip when the value
is a meaningful primitive string representation longer than 10 characters, while
preserving the existing type and length checks.

In `@src/renderer/glsl/useGLSLUniforms.test.ts`:
- Around line 21-32: Update makeGlslNode to use a real Subgraph and LiteGraph
links from the shared test factories instead of overriding getInputLink with a
hand-rolled inputs-to-subgraph mapping. Remove the getInputLink mock so
extractUniformSources exercises the actual LGraphNode.getInputLink behavior,
while preserving the test’s intended input-link setup.

In `@src/services/litegraphService.ts`:
- Around line 467-485: Extract the duplicated serialized-output merge into a
module-level helper, such as mergeSerialisedOutputs, preserving the existing
zip, RESERVED_KEYS merge, extra-output handling, and outputAsSerialisable
fallback. Replace the inline blocks in src/services/litegraphService.ts:467-485
and src/services/litegraphService.ts:579-597 with calls to the shared helper.

In `@src/stores/widgetValueStore.graphReactivity.test.ts`:
- Around line 279-293: Strengthen the test “registers plain render metadata for
non-promoted widgets” by replacing the definedness and absent sourceExecutionId
checks with assertions for the expected non-promoted render metadata, including
hasLayoutSize: false and isDOMWidget: false. Ensure the assertions distinguish
plain registration from promoted widget registration rather than only checking
defaults.

In `@src/stores/widgetValueStore.ts`:
- Around line 146-151: Update getWidgetRenderState to validate widgetId with the
existing isWidgetId guard before calling parseWidgetId; return undefined for
non-conforming IDs, matching getWidget’s behavior, while preserving the current
graph-state lookup for valid IDs.
- Around line 61-69: Split lazy creation from reads in the widget-order store:
add an ensure helper for mutation paths and a non-mutating peek helper for
reads. Update appendNodeWidgetOrder and setNodeWidgetOrder to use
ensureNodeWidgetOrder, and getNodeWidgetIds and reconcileNodeWidgetOrder to use
peekNodeWidgetOrder so computed reads do not insert entries. Apply the same
ensure/peek separation to getWidgetRenderState, ensuring read access does not
create per-graph maps.

In `@src/stores/workspace/favoritedWidgetsStore.ts`:
- Around line 76-79: Track the deferred migration from (nodeLocatorId,
widgetName) keys to host-scoped WidgetId keys by opening an issue that documents
the favoritedWidgets persistence-format change, required one-time migration of
workflow.extra.favoritedWidgets, and removal of the
isShownOnParents/favoriteNode indirection for promoted widgets.

In `@src/systems/badgeSystem.pricing.test.ts`:
- Around line 13-22: The getNodeDisplayPrice mock currently reads
useLinkStore().isInputSlotConnected, allowing the badge computed to react
without touchPricingSources registering the dependency. Make getNodeDisplayPrice
pure by deriving its result from node data or a plain test-controlled
counter/override, then update the test setup to flip that value and assert
recomputation specifically through touchPricingSources.

In `@src/systems/badgeSystem.ts`:
- Around line 212-220: Update the Settings map entries for
Comfy.NodeBadge.NodeIdBadgeMode, Comfy.NodeBadge.NodeLifeCycleBadgeMode, and
Comfy.NodeBadge.NodeSourceBadgeMode to use NodeBadgeMode, then remove the
corresponding assertions from the badgeModes initialization. Ensure
settingStore.get infers and returns NodeBadgeMode directly for these keys.
- Around line 167-187: Update gatherSubgraphCredits to touch all dynamic pricing
dependencies for the single inner API leaf, not only
pricing.getNodeRevisionRef(leaf.id). Reuse the dependency-registration behavior
from touchPricingSources, passing the appropriate inner graph-id keys from
wrapper.subgraph for priced widget values and input connections, while
preserving the existing multi-leaf and display-price behavior.

---

Outside diff comments:
In `@src/renderer/extensions/vueNodes/components/NodeSlots.vue`:
- Around line 10-19: In NodeSlots.vue, add computed inputRows and outputRows
that resolve each slot’s actual index once and calculate connection state using
the rootGraphId within the computed mapping. Update the InputSlot and OutputSlot
v-for blocks to consume these row objects for keys, slot data, indices, and
connected state, removing repeated getActualInputIndex calls and redundant
rootGraphId guards from isInputConnected/isOutputConnected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 77516a24-b905-4d0c-b70f-f93cbf262d19

📥 Commits

Reviewing files that changed from the base of the PR and between 4c643e9 and 96ba092.

⛔ Files ignored due to path filters (14)
  • browser_tests/tests/domWidget.spec.ts-snapshots/focus-mode-on-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/execution.spec.ts-snapshots/execution-error-unconnected-slot-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/graphCanvasMenu.spec.ts-snapshots/canvas-with-visible-links-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-default-workflow-mobile-chrome-linux.png is excluded by !**/*.png
  • browser_tests/tests/mobileBaseline.spec.ts-snapshots/mobile-graph-canvas-toolbar-mobile-chrome-linux.png is excluded by !**/*.png
  • browser_tests/tests/nodeBadge.spec.ts-snapshots/node-badge-left-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/saveImageAndWebp.spec.ts-snapshots/save-image-and-webm-preview-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-nodes-border-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-multiple-selections-border-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/selectionToolbox.spec.ts-snapshots/selection-toolbox-single-node-no-border-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/viewport.spec.ts-snapshots/viewport-fits-when-saved-offscreen-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/canvas/pan.spec.ts-snapshots/vue-nodes-paned-with-touch-mobile-chrome-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 (272)
  • browser_tests/assets/missing/missing_model_nested_promoted_widget.json
  • browser_tests/assets/missing/missing_models_in_subgraph.json
  • browser_tests/assets/nodes/duplicate_node_ids.json
  • browser_tests/fixtures/helpers/NodeOperationsHelper.ts
  • browser_tests/tests/appModeBuilder.spec.ts
  • browser_tests/tests/nodeBadge.spec.ts
  • browser_tests/tests/nodeDisplay.spec.ts
  • browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts
  • browser_tests/tests/propertiesPanel/errorsTabModeAware.spec.ts
  • browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts
  • browser_tests/tests/vueNodes/nodeStates/registration.spec.ts
  • browser_tests/tests/vueNodes/widgets/legacy.spec.ts
  • browser_tests/tests/vueNodes/widgets/widgetReactivity.spec.ts
  • browser_tests/tests/workflowPersistence.spec.ts
  • docs/adr/0008-entity-component-system.md
  • docs/architecture/domain-glossary.md
  • docs/architecture/ecs-lifecycle-scenarios.md
  • docs/architecture/ecs-migration-plan.md
  • docs/architecture/ecs-target-architecture.md
  • docs/architecture/entity-interactions.md
  • docs/architecture/entity-problems.md
  • docs/architecture/link-topology-store.md
  • docs/architecture/node-badge-store.md
  • docs/architecture/node-data-store.md
  • docs/architecture/output-slot-connectivity.md
  • docs/architecture/proto-ecs-stores.md
  • docs/architecture/reroute-chain-store.md
  • src/components/builder/AppModeWidgetList.vue
  • src/components/graph/GraphCanvas.vue
  • src/components/graph/widgets/domWidgetZIndex.test.ts
  • src/components/rightSidePanel/errors/useErrorGroups.test.ts
  • src/components/rightSidePanel/errors/useErrorGroups.ts
  • src/components/rightSidePanel/parameters/SectionWidgets.vue
  • src/components/rightSidePanel/parameters/TabSubgraphInputs.test.ts
  • src/components/rightSidePanel/parameters/TabSubgraphInputs.vue
  • src/components/rightSidePanel/parameters/WidgetActions.test.ts
  • src/components/rightSidePanel/parameters/WidgetActions.vue
  • src/components/rightSidePanel/parameters/WidgetItem.test.ts
  • src/components/rightSidePanel/parameters/WidgetItem.vue
  • src/components/rightSidePanel/subgraph/SubgraphEditor.test.ts
  • src/components/rightSidePanel/subgraph/SubgraphEditor.vue
  • src/composables/graph/useErrorClearingHooks.test.ts
  • src/composables/graph/useErrorClearingHooks.ts
  • src/composables/graph/useGraphNodeManager.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/composables/graph/useNodeErrorFlagSync.test.ts
  • src/composables/graph/useVueNodeLifecycle.test.ts
  • src/composables/graph/useVueNodeLifecycle.ts
  • src/composables/node/useNodeBadge.ts
  • src/composables/node/useNodePricing.test.ts
  • src/composables/node/useNodePricing.ts
  • src/composables/node/usePriceBadge.test.ts
  • src/composables/node/usePriceBadge.ts
  • src/composables/useUpstreamValue.test.ts
  • src/composables/useUpstreamValue.ts
  • src/core/graph/subgraph/migration/proxyWidgetMigration.test.ts
  • src/core/graph/subgraph/migration/proxyWidgetMigration.ts
  • src/core/graph/subgraph/promotedInputWidget.ts
  • src/core/graph/subgraph/promotionUtils.test.ts
  • src/core/graph/subgraph/promotionUtils.ts
  • src/core/graph/subgraph/resolveConcretePromotedWidget.test.ts
  • src/core/graph/subgraph/resolveSubgraphInputLink.ts
  • src/core/graph/widgets/dynamicWidgets.test.ts
  • src/core/graph/widgets/dynamicWidgets.ts
  • src/core/graph/widgets/matchTypeConfiguring.test.ts
  • src/extensions/core/groupNode.ts
  • src/extensions/core/load3d.ts
  • src/extensions/core/rerouteNode.ts
  • src/extensions/core/saveImageExtraOutput.test.ts
  • src/extensions/core/widgetInputs.test.ts
  • src/extensions/core/widgetInputs.ts
  • src/extensions/core/widgetValuePropagation.test.ts
  • src/extensions/core/widgetValuePropagation.ts
  • src/lib/litegraph/src/LGraph.inputSlotRealign.test.ts
  • src/lib/litegraph/src/LGraph.serialise.test.ts
  • src/lib/litegraph/src/LGraph.test.ts
  • src/lib/litegraph/src/LGraph.ts
  • src/lib/litegraph/src/LGraphBadge.ts
  • src/lib/litegraph/src/LGraphCanvas.clipboard.test.ts
  • src/lib/litegraph/src/LGraphCanvas.cloneZIndex.test.ts
  • src/lib/litegraph/src/LGraphCanvas.drawConnections.test.ts
  • src/lib/litegraph/src/LGraphCanvas.ghost.test.ts
  • src/lib/litegraph/src/LGraphCanvas.groupSelection.test.ts
  • src/lib/litegraph/src/LGraphCanvas.slotHitDetection.test.ts
  • src/lib/litegraph/src/LGraphCanvas.ts
  • src/lib/litegraph/src/LGraphNode.nodeState.test.ts
  • src/lib/litegraph/src/LGraphNode.test.ts
  • src/lib/litegraph/src/LGraphNode.ts
  • src/lib/litegraph/src/LGraphNodeProperties.test.ts
  • src/lib/litegraph/src/LGraphNodeProperties.ts
  • src/lib/litegraph/src/LLink.store.test.ts
  • src/lib/litegraph/src/LLink.test.ts
  • src/lib/litegraph/src/LLink.ts
  • src/lib/litegraph/src/LiteGraphGlobal.ts
  • src/lib/litegraph/src/Reroute.store.test.ts
  • src/lib/litegraph/src/Reroute.ts
  • src/lib/litegraph/src/__fixtures__/nodeHelpers.ts
  • src/lib/litegraph/src/canvas/FloatingRenderLink.test.ts
  • src/lib/litegraph/src/canvas/FloatingRenderLink.ts
  • src/lib/litegraph/src/canvas/LinkConnector.core.test.ts
  • src/lib/litegraph/src/canvas/LinkConnector.integration.test.ts
  • src/lib/litegraph/src/canvas/LinkConnector.test.ts
  • src/lib/litegraph/src/canvas/LinkConnector.ts
  • src/lib/litegraph/src/canvas/LinkConnectorSubgraphInputValidation.test.ts
  • src/lib/litegraph/src/canvas/ToInputFromIoNodeLink.ts
  • src/lib/litegraph/src/canvas/ToInputRenderLink.ts
  • src/lib/litegraph/src/infrastructure/LGraphEventMap.ts
  • src/lib/litegraph/src/interfaces.ts
  • src/lib/litegraph/src/linkDeduplication.ts
  • src/lib/litegraph/src/litegraph.ts
  • src/lib/litegraph/src/node/NodeInputSlot.test.ts
  • src/lib/litegraph/src/node/NodeInputSlot.ts
  • src/lib/litegraph/src/node/NodeOutputSlot.test.ts
  • src/lib/litegraph/src/node/NodeOutputSlot.ts
  • src/lib/litegraph/src/node/NodeSlot.test.ts
  • src/lib/litegraph/src/node/NodeSlot.ts
  • src/lib/litegraph/src/node/SlotBase.ts
  • src/lib/litegraph/src/node/slotEcosystemPatterns.test.ts
  • src/lib/litegraph/src/node/slotLinks.test.ts
  • src/lib/litegraph/src/node/slotLinks.ts
  • src/lib/litegraph/src/node/slotUtils.test.ts
  • src/lib/litegraph/src/node/slotUtils.ts
  • src/lib/litegraph/src/nodeBadgeDraw.test.ts
  • src/lib/litegraph/src/nodeBadgeDraw.ts
  • src/lib/litegraph/src/serialization.test.ts
  • src/lib/litegraph/src/subgraph/ExecutableNodeDTO.test.ts
  • src/lib/litegraph/src/subgraph/ExecutableNodeDTO.ts
  • src/lib/litegraph/src/subgraph/SubgraphConversion.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphIO.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphInput.ts
  • src/lib/litegraph/src/subgraph/SubgraphInputNode.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts
  • src/lib/litegraph/src/subgraph/SubgraphOutput.ts
  • src/lib/litegraph/src/subgraph/SubgraphSerialization.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphSlotConnections.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphWidgetPromotion.test.ts
  • src/lib/litegraph/src/subgraph/__fixtures__/README.md
  • src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.test.ts
  • src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts
  • src/lib/litegraph/src/subgraph/subgraphDeduplication.test.ts
  • src/lib/litegraph/src/subgraph/subgraphDeduplication.ts
  • src/lib/litegraph/src/subgraph/subgraphUtils.test.ts
  • src/lib/litegraph/src/subgraph/subgraphUtils.ts
  • src/lib/litegraph/src/types/graphTriggers.ts
  • src/lib/litegraph/src/types/serialisation.ts
  • src/lib/litegraph/src/types/widgets.ts
  • src/lib/litegraph/src/utils/collections.test.ts
  • src/lib/litegraph/src/utils/collections.ts
  • src/lib/litegraph/src/utils/widget.ts
  • src/lib/litegraph/src/widgets/BaseWidget.test.ts
  • src/lib/litegraph/src/widgets/BaseWidget.ts
  • src/locales/ar/main.json
  • src/locales/en/main.json
  • src/locales/es/main.json
  • src/locales/fa/main.json
  • src/locales/fr/main.json
  • src/locales/he/main.json
  • src/locales/ja/main.json
  • src/locales/ko/main.json
  • src/locales/pt-BR/main.json
  • src/locales/ru/main.json
  • src/locales/tr/main.json
  • src/locales/zh-TW/main.json
  • src/locales/zh/main.json
  • src/platform/cloud/subscription/composables/useFreeTierQuota.ts
  • src/platform/missingMedia/missingMediaStore.ts
  • src/platform/missingModel/missingModelScan.test.ts
  • src/platform/missingModel/missingModelScan.ts
  • src/platform/missingModel/missingModelStore.ts
  • src/platform/nodeReplacement/useNodeReplacement.test.ts
  • src/platform/nodeReplacement/useNodeReplacement.ts
  • src/platform/telemetry/nodeAdded/installNodeAddedTelemetry.test.ts
  • src/platform/telemetry/nodeAdded/installNodeAddedTelemetry.ts
  • src/platform/workflow/core/services/workflowService.insertWorkflow.test.ts
  • src/platform/workflow/core/services/workflowService.ts
  • src/renderer/core/canvas/canvasStore.test.ts
  • src/renderer/core/canvas/canvasStore.ts
  • src/renderer/core/canvas/links/linkConnectorAdapter.ts
  • src/renderer/core/layout/operations/layoutMutations.test.ts
  • src/renderer/core/layout/operations/layoutMutations.ts
  • src/renderer/core/layout/store/layoutStore.test.ts
  • src/renderer/core/layout/store/layoutStore.ts
  • src/renderer/core/layout/types.ts
  • src/renderer/extensions/linearMode/PartnerNodesList.vue
  • src/renderer/extensions/minimap/composables/useMinimap.test.ts
  • src/renderer/extensions/minimap/composables/useMinimapGraph.test.ts
  • src/renderer/extensions/minimap/composables/useMinimapGraph.ts
  • src/renderer/extensions/minimap/data/AbstractMinimapDataSource.ts
  • src/renderer/extensions/minimap/data/LayoutStoreDataSource.ts
  • src/renderer/extensions/minimap/data/MinimapDataSource.test.ts
  • src/renderer/extensions/minimap/data/MinimapDataSourceFactory.ts
  • src/renderer/extensions/minimap/minimapCanvasRenderer.test.ts
  • src/renderer/extensions/vueNodes/components/LGraphNode.subgraph.test.ts
  • src/renderer/extensions/vueNodes/components/LGraphNode.test.ts
  • src/renderer/extensions/vueNodes/components/LGraphNode.vue
  • src/renderer/extensions/vueNodes/components/LGraphNodePreview.test.ts
  • src/renderer/extensions/vueNodes/components/LGraphNodePreview.vue
  • src/renderer/extensions/vueNodes/components/NodeContent.vue
  • src/renderer/extensions/vueNodes/components/NodeHeader.test.ts
  • src/renderer/extensions/vueNodes/components/NodeHeader.vue
  • src/renderer/extensions/vueNodes/components/NodeSlots.test.ts
  • src/renderer/extensions/vueNodes/components/NodeSlots.vue
  • src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/components/WidgetGrid.vue
  • src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts
  • src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts
  • src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts
  • src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts
  • src/renderer/extensions/vueNodes/composables/useNodeTooltips.test.ts
  • src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts
  • src/renderer/extensions/vueNodes/composables/usePartitionedBadges.test.ts
  • src/renderer/extensions/vueNodes/composables/usePartitionedBadges.ts
  • src/renderer/extensions/vueNodes/composables/useProcessedWidgets.test.ts
  • src/renderer/extensions/vueNodes/composables/useProcessedWidgets.ts
  • src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.autoPan.test.ts
  • src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts
  • src/renderer/extensions/vueNodes/layout/nodeSizeReflow.test.ts
  • src/renderer/extensions/vueNodes/types/widgetGrid.ts
  • src/renderer/extensions/vueNodes/utils/__tests__/nodeDataUtils.test.ts
  • src/renderer/extensions/vueNodes/utils/nodeDataUtils.ts
  • src/renderer/extensions/vueNodes/utils/nodeErrorState.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetButton.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetDOM.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/WidgetDOM.vue
  • src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useProgressTextWidget.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts
  • src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts
  • src/renderer/glsl/glslPreviewUtils.ts
  • src/renderer/glsl/useGLSLUniforms.test.ts
  • src/renderer/glsl/useGLSLUniforms.ts
  • src/scripts/app.test.ts
  • src/scripts/app.ts
  • src/scripts/promotedWidgetControl.test.ts
  • src/scripts/promotedWidgetControl.ts
  • src/scripts/widgets.ts
  • src/services/litegraphService.ts
  • src/stores/appModeStore.test.ts
  • src/stores/linkStore.test.ts
  • src/stores/linkStore.ts
  • src/stores/nodeDataStore.test.ts
  • src/stores/nodeDataStore.ts
  • src/stores/rerouteStore.test.ts
  • src/stores/rerouteStore.ts
  • src/stores/widgetValueStore.graphReactivity.test.ts
  • src/stores/widgetValueStore.test.ts
  • src/stores/widgetValueStore.ts
  • src/stores/workspace/favoritedWidgetsStore.ts
  • src/systems/badgeSystem.pricing.test.ts
  • src/systems/badgeSystem.subgraph.test.ts
  • src/systems/badgeSystem.test.ts
  • src/systems/badgeSystem.ts
  • src/types/badgeData.ts
  • src/types/linkTopology.ts
  • src/types/nodeState.ts
  • src/types/rerouteChain.ts
  • src/types/simplifiedWidget.ts
  • src/types/widgetState.ts
  • src/utils/__tests__/litegraphTestUtils.ts
  • src/utils/graphTraversalUtil.test.ts
  • src/utils/graphTraversalUtil.ts
  • src/utils/linkFixer.test.ts
  • src/utils/linkFixer.ts
  • src/utils/litegraphUtil.test.ts
  • src/utils/litegraphUtil.ts
  • src/utils/searchAndReplace.test.ts
  • src/utils/widgetUtil.ts
  • tools/devtools/web/legacyWidget.js
💤 Files with no reviewable changes (27)
  • src/lib/litegraph/src/LGraphNodeProperties.test.ts
  • src/composables/node/usePriceBadge.test.ts
  • src/composables/node/usePriceBadge.ts
  • src/lib/litegraph/src/LGraphNodeProperties.ts
  • src/lib/litegraph/src/LGraphBadge.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/composables/graph/useGraphNodeManager.test.ts
  • browser_tests/tests/nodeBadge.spec.ts
  • src/locales/ru/main.json
  • src/lib/litegraph/src/canvas/ToInputRenderLink.ts
  • src/locales/ar/main.json
  • src/locales/ja/main.json
  • src/locales/zh/main.json
  • src/lib/litegraph/src/types/graphTriggers.ts
  • src/lib/litegraph/src/node/SlotBase.ts
  • src/locales/zh-TW/main.json
  • src/locales/tr/main.json
  • src/lib/litegraph/src/canvas/ToInputFromIoNodeLink.ts
  • src/locales/es/main.json
  • src/locales/fr/main.json
  • src/locales/fa/main.json
  • src/scripts/app.ts
  • browser_tests/fixtures/helpers/NodeOperationsHelper.ts
  • src/renderer/core/layout/types.ts
  • src/locales/ko/main.json
  • src/locales/pt-BR/main.json
  • src/locales/he/main.json
🛑 Comments failed to post (1)
src/renderer/extensions/vueNodes/composables/useProcessedWidgets.test.ts (1)

342-351: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid asserting raw Tailwind class strings.

'ring ring-component-node-widget-advanced' is hard-coded here and again at Line 468, so a purely cosmetic class rename breaks both tests without any behavior change. Prefer asserting the advanced/non-advanced distinction (e.g. compare borderStyle for showAdvanced vs. a non-advanced widget, or expose the class via a shared constant that both source and test import).

As per coding guidelines, "Do not write change-detector tests, style-dependent tests, redundant tests".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/extensions/vueNodes/composables/useProcessedWidgets.test.ts`
around lines 342 - 351, Update the test around processWidgets to avoid asserting
the literal Tailwind value ring ring-component-node-widget-advanced. Verify the
advanced/non-advanced borderStyle behavior by comparing results for showAdvanced
or reuse a shared source constant, and apply the same change to the duplicate
assertion near the other advanced-widget test.

Source: Coding guidelines

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.57447% with 87 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/lib/litegraph/src/LGraph.ts 79.85% 53 Missing and 3 partials ⚠️
src/core/graph/widgets/dynamicWidgets.ts 89.85% 7 Missing ⚠️
src/composables/graph/useArrangeNodes.ts 40.00% 3 Missing ⚠️
src/composables/node/useNodeBadge.ts 40.00% 3 Missing ⚠️
src/core/graph/subgraph/promotionUtils.ts 86.36% 3 Missing ⚠️
...components/rightSidePanel/errors/useErrorGroups.ts 33.33% 2 Missing ⚠️
src/composables/graph/useNodeArrangement.ts 0.00% 2 Missing ⚠️
src/composables/node/useNodePricing.ts 92.30% 2 Missing ⚠️
src/extensions/core/widgetInputs.ts 92.00% 2 Missing ⚠️
src/extensions/core/widgetValuePropagation.ts 81.81% 2 Missing ⚠️
... and 5 more
@@            Coverage Diff             @@
##             main   #14246      +/-   ##
==========================================
+ Coverage   79.39%   82.31%   +2.91%     
==========================================
  Files        2217     1900     -317     
  Lines      112188   107652    -4536     
  Branches    35091    34061    -1030     
==========================================
- Hits        89069    88610     -459     
+ Misses      22640    18706    -3934     
+ Partials      479      336     -143     
Flag Coverage Δ
e2e 67.96% <80.59%> (+0.48%) ⬆️
unit 73.93% <69.28%> (+0.50%) ⬆️
website-unit ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/components/graph/widgets/DomWidget.vue 90.69% <ø> (+1.16%) ⬆️
...nents/rightSidePanel/parameters/SectionWidgets.vue 85.43% <ø> (+0.76%) ⬆️
...ts/rightSidePanel/parameters/TabSubgraphInputs.vue 76.47% <ø> (-0.35%) ⬇️
...onents/rightSidePanel/parameters/WidgetActions.vue 92.45% <100.00%> (+25.34%) ⬆️
...ponents/rightSidePanel/subgraph/SubgraphEditor.vue 85.29% <100.00%> (-0.29%) ⬇️
.../composables/canvas/useSelectionToolboxPosition.ts 69.56% <100.00%> (-1.67%) ⬇️
src/composables/graph/useErrorClearingHooks.ts 96.75% <100.00%> (-0.70%) ⬇️
...e/graph/subgraph/migration/proxyWidgetMigration.ts 91.66% <100.00%> (-0.15%) ⬇️
src/core/graph/subgraph/promotedInputWidget.ts 78.94% <100.00%> (-3.28%) ⬇️
...rc/core/graph/subgraph/resolveSubgraphInputLink.ts 100.00% <100.00%> (ø)
... and 81 more

... and 416 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

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

⚠️ Outside diff range comments (8)
src/extensions/core/groupNode.ts (1)

926-931: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Resolve links from the graph that owns the group node.

convertToNodes() documents nested-subgraph support, but reconnectInputs() resolves the origin through app.rootGraph, and reconnectOutputs() queries outputLinks(app.rootGraph, ...). Link and node collections are graph-local. For a group inside a Subgraph, these lookups cannot resolve the subgraph topology, so unpacking can drop connections. Resolve both endpoints from the owning graph and explicitly map them into the root graph before reconnecting.

Also applies to: 943-953

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extensions/core/groupNode.ts` around lines 926 - 931, Update
reconnectInputs() and reconnectOutputs() to use the graph owning the group node,
rather than app.rootGraph, when resolving links, origins, and output links.
Before reconnecting, explicitly map the resolved endpoints from that owning
graph into the root graph so nested Subgraph topology is preserved during
convertToNodes().
src/extensions/core/widgetInputs.ts (1)

348-354: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Skip links whose target slot no longer exists.

theirNode can resolve while theirNode.inputs[link.target_slot] is undefined after input layout changes or legacy workflow restoration. _isValidConnection() reads input.widget, so this path can throw during _mergeWidgetConfig(). Add a guard before calling it.

Proposed fix
       const theirInput = theirNode.inputs[link.target_slot]
+      if (!theirInput) continue

       // Call is valid connection so it can merge the configs when validating
       this._isValidConnection(theirInput, hasConfig)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extensions/core/widgetInputs.ts` around lines 348 - 354, In the
link-processing loop within _mergeWidgetConfig, guard the
theirNode.inputs[link.target_slot] lookup and skip the link when the target slot
is missing, before calling _isValidConnection. Preserve processing for links
with an existing target input.
src/renderer/core/layout/store/layoutStore.ts (2)

239-242: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent no-op on null assignment can hide a real caller.

The setter ignores null instead of emitting deleteNode. The comment asserts that no caller assigns null. The ref type still allows null, so a future caller gets a silent no-op with no signal. Narrow the ref type to Ref<NodeLayout | null> for reads only, or log at debug level when a null write arrives.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/core/layout/store/layoutStore.ts` around lines 239 - 242, Update
the layout ref setter in the layout store to avoid silently ignoring null
assignments: either expose a read-only Ref<NodeLayout | null> type so callers
cannot write null, or retain the setter and emit a debug log when newLayout is
null. Preserve layoutMutations.deleteNode as the deletion path for valid node
deletions.

1136-1158: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Clean up floating-link geometry when removing a floating link.

removeFloatingLink removes topology but does not remove linkSegmentLayouts or linkSegmentSpatialIndex entries. Add this cleanup to removeFloatingLink. Make the cleanup unconditional when no whole-link layout exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/core/layout/store/layoutStore.ts` around lines 1136 - 1158, The
node deletion path leaves floating-link geometry entries behind because
removeFloatingLink does not clear linkSegmentLayouts or linkSegmentSpatialIndex.
Update removeFloatingLink to remove the affected floating link’s segment layout
and spatial-index entries, performing this cleanup unconditionally when no
whole-link layout exists; leave whole-link layout handling unchanged.
docs/architecture/reroute-chain-store.md (1)

111-118: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the reroute geometry scope statement.

Reroute.pos no longer mirrors a class-owned position field. layoutStore owns reroute position. State that reroute geometry is out of scope because it has already migrated, not because duplication remains.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/reroute-chain-store.md` around lines 111 - 118, Update the
Scope section to state that reroute geometry is out of scope because reroute
position has already migrated to layoutStore ownership; remove the inaccurate
claim that Reroute.pos mirrors a class field and that pre-existing duplication
remains.
src/lib/litegraph/src/LGraphCanvas.ts (1)

2530-2572: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Reroute hit-testing on left-click duplicates the extracted findRerouteAtPoint helper.

processMouseDown's right-click path resolves reroutes through findRerouteAtPoint(graph, x, y, this._visibleReroutes). _processPrimaryButton re-implements the same "query layout store, then fall back" pattern inline instead of reusing that helper. Keep the two click paths on one hit-testing implementation. Otherwise, future changes to findRerouteAtPoint's fallback behavior will not propagate to the left-click path, and the two paths can silently diverge on edge positions.

Consider extracting the "resolve reroute by id from a layout hit" step into a small helper that both findRerouteAtPoint and this loop call, so the layout-store resolution logic stays in one place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/litegraph/src/LGraphCanvas.ts` around lines 2530 - 2572, Update the
reroute hit-testing in _processPrimaryButton to reuse findRerouteAtPoint instead
of duplicating the layout-store query and visible-reroute fallback. If needed,
extract the shared layout-hit ID resolution into a small helper used by both
findRerouteAtPoint and the primary-button path, while preserving the existing
click, drag, connector, and slot-hover behavior.
src/lib/litegraph/src/Reroute.ts (1)

674-685: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Handle duplicate reroute IDs before replacing the graph entry.

registerReroute() refuses to replace an existing chain but returns the unregistered chain. registerRerouteChain() then assigns that chain to the new reroute, while _addReroute() overwrites the graph map. Unregistering the new reroute cannot remove the old store registration, leaving an orphaned chain.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/litegraph/src/Reroute.ts` around lines 674 - 685, Update the reroute
registration flow around registerReroute(), registerRerouteChain(), and
_addReroute() so duplicate IDs are handled before the graph map is overwritten.
Preserve the existing registered chain when an ID already exists, and ensure the
new reroute is not assigned an unregistered chain or left able to orphan the old
store registration.
src/renderer/core/layout/operations/layoutMutations.ts (1)

236-285: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add existence guards to createGroup and deleteGroup for consistency.

setGroupBounds returns early when the group layout is missing. createGroup and deleteGroup always apply an operation. A deleteGroup call for an unknown group still appends an entry to the operation log and notifies listeners with change.type = 'delete', although the Yjs delete is a no-op. Node mutations (deleteNode) use the same guard pattern in this file.

♻️ Proposed guard for `deleteGroup`
   const deleteGroup = (rootGraphId: UUID, groupId: GroupId): void => {
+    if (!layoutStore.getGroupLayout(rootGraphId, groupId)) return
+
     layoutStore.applyOperation({
       type: 'deleteGroup',
       entity: 'group',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/core/layout/operations/layoutMutations.ts` around lines 236 -
285, Update createGroup and deleteGroup to check whether the group already
exists using layoutStore.getGroupLayout(rootGraphId, groupId) before applying
operations, matching the guard pattern in setGroupBounds and deleteNode.
Preserve createGroup’s existing behavior for valid groups and return early when
the requested group state does not satisfy the intended existence condition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@browser_tests/tests/copyPaste.spec.ts`:
- Around line 33-56: Update the test around the pinned node copy/paste flow to
capture the original node’s distinct ID and position before pasting, then
identify the newly pasted node by ID rather than relying on getNodeRefsByType
ordering. After confirming the node count, derive the expected graph coordinate
from the cursor position and assert the pasted node’s position matches it, while
retaining the original-node identity checks.

In `@src/lib/litegraph/src/LGraph.ts`:
- Around line 508-524: Update the subgraph-definition cleanup in remove() to
delete each released definition’s groups and reroutes through
useLayoutMutations(), using the root graph ID and the same LayoutSource.Canvas
setup as clear(). Ensure this runs before the definition becomes unreachable,
while preserving the existing topology and node-state cleanup.

In `@src/lib/litegraph/src/LGraphCanvas.ts`:
- Around line 8944-8950: Update applyNodePositions to build the corresponding
node-position updates and commit them through
layoutMutations.batchMoveNodes(updates) once, instead of calling node.setPos for
each node. Preserve the existing positions mapping and observable node
coordinates while batching the layout-store write.

In `@src/lib/litegraph/src/LGraphGroup.test.ts`:
- Around line 171-182: The mutation tests around the `test.for(mutations)` block
and the additional cases at lines 204–221 currently derive expected geometry
from `group.pos` and `group.size`, making the assertions self-referential.
Replace those fields with the independently known literal x, y, width, and
height values for each mutation case, matching the established legacy geometry
buffer test pattern.

In `@src/lib/litegraph/src/LGraphGroup.ts`:
- Around line 168-176: Remove the explicit syncBoundsFromStore() calls from both
the boundingRect getter and getBounding() method in LGraphGroup, returning
this._bounding directly. Preserve the Proxy-based synchronization performed when
callers access geometry properties.

In `@src/lib/litegraph/src/LGraphNode.ts`:
- Around line 650-659: Scope node layout refs, Y nodes, indexes, listeners, and
lifecycle operations by rootGraph.id rather than nodeId alone, updating
LGraphNode methods _positionUpdated and the related layout-operation site at
LGraphNode.ts lines 680-690 while preserving existing behavior. Add an isolation
test in LGraphNode.test.ts lines 794-864 using identical node IDs in two root
graphs and verify position and size updates remain isolated.

In `@src/platform/workflow/core/utils/workflowToClipboardItems.test.ts`:
- Around line 101-140: Add a cyclic-subgraph test alongside the existing nesting
test, using the subgraph helper to create A nesting B and B nesting A, then pass
the resulting workflow through workflowToClipboardItems and assert flattening
terminates with each subgraph ID included exactly once and no nested definitions
retained.

In `@src/platform/workflow/core/utils/workflowToClipboardItems.ts`:
- Around line 12-27: The workflow insertion path must preserve root-level
floating links: update workflowToClipboardItems and the corresponding
_deserializeItems processing so graph.floatingLinks are carried through and
inserted alongside parsed.links. Add a regression test covering a workflow with
root-level floating links, rather than only extending ClipboardItems.

In `@src/renderer/core/layout/store/layoutStore.ts`:
- Around line 1000-1003: Update the workflow close cleanup in
initializeFromLiteGraph to remove discarded root graph IDs from both
rerouteLayouts and rerouteSpatialIndex, while preserving reroute geometry during
subgraph navigation and retaining the existing link, segment, and slot layout
cleanup.

In `@src/renderer/core/layout/sync/useLayoutSync.test.ts`:
- Around line 111-146: Replace the `canvas as never` cast in the `startSync`
call with a typed partial mock created via `fromPartial` from
`@total-typescript/shoehorn`; use the same helper for the `liteNode` mock so
both objects are checked against their expected contracts while preserving the
existing position-setter assertion.

In `@src/renderer/core/layout/utils/mappers.ts`:
- Around line 56-96: Share the rectangle serialization and deserialization logic
between group and node layouts by extracting or reusing the existing helpers
used by layoutToYNode and yNodeRect. Update layoutToYGroup and setYGroupRect to
use the shared rect writer, and update yGroupToLayout to use the shared rect
reader so the StoredRect cast is centralized.

In `@src/renderer/extensions/minimap/data/LayoutStoreDataSource.ts`:
- Around line 75-77: Update getNodeCount() to count entries from viewedNodes()
that have a layout, rather than calling getNodes() and constructing full
MinimapNodeData objects; preserve the same layout-presence filtering used by
getNodes().

In `@src/renderer/extensions/vueNodes/layout/useNodeDrag.test.ts`:
- Line 114: Update the isLGraphNode stub used by the startDrag tests to
distinguish actual LGraphNode instances, preferably using the real LiteGraph
class or shared factory instead of a constant mock. Add a selected LGraphNode to
the relevant selectedItems case and assert it is excluded from
nonNodeStartPositions, ensuring the !isLGraphNode filter cannot be inverted or
omitted without failing.

---

Outside diff comments:
In `@docs/architecture/reroute-chain-store.md`:
- Around line 111-118: Update the Scope section to state that reroute geometry
is out of scope because reroute position has already migrated to layoutStore
ownership; remove the inaccurate claim that Reroute.pos mirrors a class field
and that pre-existing duplication remains.

In `@src/extensions/core/groupNode.ts`:
- Around line 926-931: Update reconnectInputs() and reconnectOutputs() to use
the graph owning the group node, rather than app.rootGraph, when resolving
links, origins, and output links. Before reconnecting, explicitly map the
resolved endpoints from that owning graph into the root graph so nested Subgraph
topology is preserved during convertToNodes().

In `@src/extensions/core/widgetInputs.ts`:
- Around line 348-354: In the link-processing loop within _mergeWidgetConfig,
guard the theirNode.inputs[link.target_slot] lookup and skip the link when the
target slot is missing, before calling _isValidConnection. Preserve processing
for links with an existing target input.

In `@src/lib/litegraph/src/LGraphCanvas.ts`:
- Around line 2530-2572: Update the reroute hit-testing in _processPrimaryButton
to reuse findRerouteAtPoint instead of duplicating the layout-store query and
visible-reroute fallback. If needed, extract the shared layout-hit ID resolution
into a small helper used by both findRerouteAtPoint and the primary-button path,
while preserving the existing click, drag, connector, and slot-hover behavior.

In `@src/lib/litegraph/src/Reroute.ts`:
- Around line 674-685: Update the reroute registration flow around
registerReroute(), registerRerouteChain(), and _addReroute() so duplicate IDs
are handled before the graph map is overwritten. Preserve the existing
registered chain when an ID already exists, and ensure the new reroute is not
assigned an unregistered chain or left able to orphan the old store
registration.

In `@src/renderer/core/layout/operations/layoutMutations.ts`:
- Around line 236-285: Update createGroup and deleteGroup to check whether the
group already exists using layoutStore.getGroupLayout(rootGraphId, groupId)
before applying operations, matching the guard pattern in setGroupBounds and
deleteNode. Preserve createGroup’s existing behavior for valid groups and return
early when the requested group state does not satisfy the intended existence
condition.

In `@src/renderer/core/layout/store/layoutStore.ts`:
- Around line 239-242: Update the layout ref setter in the layout store to avoid
silently ignoring null assignments: either expose a read-only Ref<NodeLayout |
null> type so callers cannot write null, or retain the setter and emit a debug
log when newLayout is null. Preserve layoutMutations.deleteNode as the deletion
path for valid node deletions.
- Around line 1136-1158: The node deletion path leaves floating-link geometry
entries behind because removeFloatingLink does not clear linkSegmentLayouts or
linkSegmentSpatialIndex. Update removeFloatingLink to remove the affected
floating link’s segment layout and spatial-index entries, performing this
cleanup unconditionally when no whole-link layout exists; leave whole-link
layout handling unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4a7d20ec-7efd-422c-9d8a-7a2840f3d714

📥 Commits

Reviewing files that changed from the base of the PR and between 2d755f0 and 15db3dd.

⛔ Files ignored due to path filters (3)
  • browser_tests/tests/saveImageAndWebp.spec.ts-snapshots/save-image-and-webm-preview-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 (59)
  • browser_tests/fixtures/helpers/CanvasHelper.ts
  • browser_tests/tests/copyPaste.spec.ts
  • browser_tests/tests/vueNodes/interactions/node/move.spec.ts
  • browser_tests/tests/vueNodes/rerouteGeometry.spec.ts
  • docs/architecture/ecs-migration-plan.md
  • docs/architecture/ecs-target-architecture.md
  • docs/architecture/proto-ecs-stores.md
  • docs/architecture/reroute-chain-store.md
  • src/composables/graph/useNodeArrangement.ts
  • src/composables/graph/useVueNodeLifecycle.ts
  • src/core/graph/widgets/dynamicWidgets.ts
  • src/extensions/core/groupNode.ts
  • src/extensions/core/widgetInputs.ts
  • src/lib/litegraph/src/LGraph.test.ts
  • src/lib/litegraph/src/LGraph.ts
  • src/lib/litegraph/src/LGraphCanvas.cloneZIndex.test.ts
  • src/lib/litegraph/src/LGraphCanvas.deprecated.test.ts
  • src/lib/litegraph/src/LGraphCanvas.groupSelection.test.ts
  • src/lib/litegraph/src/LGraphCanvas.ts
  • src/lib/litegraph/src/LGraphGroup.test.ts
  • src/lib/litegraph/src/LGraphGroup.ts
  • src/lib/litegraph/src/LGraphNode.test.ts
  • src/lib/litegraph/src/LGraphNode.ts
  • src/lib/litegraph/src/Reroute.store.test.ts
  • src/lib/litegraph/src/Reroute.ts
  • src/lib/litegraph/src/canvas/findRerouteAtPoint.ts
  • src/lib/litegraph/src/canvas/getCanvasContextMenuTarget.test.ts
  • src/lib/litegraph/src/canvas/getCanvasContextMenuTarget.ts
  • src/lib/litegraph/src/infrastructure/createGeometryView.test.ts
  • src/lib/litegraph/src/infrastructure/createGeometryView.ts
  • src/lib/litegraph/src/interfaces.ts
  • src/lib/litegraph/src/linkDeduplication.ts
  • src/lib/litegraph/src/subgraph/SubgraphIONodeBase.ts
  • src/lib/litegraph/src/subgraph/subgraphDeduplication.test.ts
  • src/lib/litegraph/src/subgraph/subgraphDeduplication.ts
  • src/platform/workflow/core/services/workflowService.insertWorkflow.test.ts
  • src/platform/workflow/core/services/workflowService.ts
  • src/platform/workflow/core/utils/workflowToClipboardItems.integration.test.ts
  • src/platform/workflow/core/utils/workflowToClipboardItems.test.ts
  • src/platform/workflow/core/utils/workflowToClipboardItems.ts
  • src/renderer/core/layout/operations/layoutMutations.ts
  • src/renderer/core/layout/store/layoutStore.test.ts
  • src/renderer/core/layout/store/layoutStore.ts
  • src/renderer/core/layout/sync/syncLayoutStoreFromGraph.test.ts
  • src/renderer/core/layout/sync/syncLayoutStoreFromGraph.ts
  • src/renderer/core/layout/sync/useLayoutSync.test.ts
  • src/renderer/core/layout/sync/useLayoutSync.ts
  • src/renderer/core/layout/types.ts
  • src/renderer/core/layout/utils/mappers.test.ts
  • src/renderer/core/layout/utils/mappers.ts
  • src/renderer/extensions/minimap/data/LayoutStoreDataSource.ts
  • src/renderer/extensions/minimap/data/MinimapDataSource.test.ts
  • src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts
  • src/renderer/extensions/vueNodes/layout/useNodeDrag.test.ts
  • src/renderer/extensions/vueNodes/layout/useNodeDrag.ts
  • src/scripts/app.ts
  • src/utils/vintageClipboard.ts
💤 Files with no reviewable changes (3)
  • src/renderer/core/layout/sync/syncLayoutStoreFromGraph.ts
  • src/renderer/core/layout/sync/syncLayoutStoreFromGraph.test.ts
  • src/composables/graph/useVueNodeLifecycle.ts

Comment thread browser_tests/tests/copyPaste.spec.ts Outdated
Comment on lines +33 to +56
test('Pasted copy of a pinned node lands at the cursor', async ({
comfyPage
}) => {
const node = (await comfyPage.nodeOps.getNodeRefsByType('KSampler'))[0]
await node.clickContextMenuOption('Pin')
await comfyPage.contextMenu.waitForHidden()
await expect.poll(() => node.isPinned()).toBe(true)

await node.click('title')
await comfyPage.page.mouse.move(10, 10)
await comfyPage.nextFrame()
await comfyPage.clipboard.copy(comfyPage.canvas)
await comfyPage.clipboard.paste(comfyPage.canvas)

await expect
.poll(
async () =>
(await comfyPage.nodeOps.getNodeRefsByType('KSampler')).length
)
.toBe(2)
const [original, pasted] =
await comfyPage.nodeOps.getNodeRefsByType('KSampler')
expect(await pasted.getPosition()).not.toEqual(await original.getPosition())
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Assert cursor placement with a stable node identity.

Lines 53-55 infer original and pasted from list order. This is not stable. The assertion also passes when paste uses any position that differs from the original position.

Capture the original node ID and position before paste. Find the new node by its distinct ID after the count assertion. Compare its position with the graph coordinate derived from the cursor position.

Based on learnings, browser test helpers must not depend on graph-node ordering. As per path instructions, tests must verify behavior rather than implementation details.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@browser_tests/tests/copyPaste.spec.ts` around lines 33 - 56, Update the test
around the pinned node copy/paste flow to capture the original node’s distinct
ID and position before pasting, then identify the newly pasted node by ID rather
than relying on getNodeRefsByType ordering. After confirming the node count,
derive the expected graph coordinate from the cursor position and assert the
pasted node’s position matches it, while retaining the original-node identity
checks.

Sources: Path instructions, Learnings

Comment thread src/lib/litegraph/src/LGraph.ts Outdated
Comment thread src/lib/litegraph/src/LGraphCanvas.ts
Comment thread src/lib/litegraph/src/LGraphGroup.test.ts
Comment thread src/lib/litegraph/src/LGraphGroup.ts
Comment thread src/renderer/core/layout/store/layoutStore.ts
Comment thread src/renderer/core/layout/sync/useLayoutSync.test.ts Outdated
Comment thread src/renderer/core/layout/utils/mappers.ts
Comment thread src/renderer/extensions/minimap/data/LayoutStoreDataSource.ts Outdated
@@ -111,7 +111,7 @@ vi.mock('@/renderer/core/layout/transform/useTransformState', () => ({
}))

vi.mock('@/utils/litegraphUtil', () => ({
isLGraphGroup: () => false
isLGraphNode: () => false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

A constant isLGraphNode: () => false stub cannot detect an inverted filter.

startDrag keeps selected items with !isLGraphNode(item). With the stub always returning false, every selected item passes the filter, so the new non-node tests also pass if the predicate is inverted or dropped. Make the stub discriminate, for example by returning item instanceof LGraphNode, and add a selected node to selectedItems in one case so that nodes are proven excluded from nonNodeStartPositions.

As per path instructions: "Prefer real LiteGraph instances/shared factories, minimize mocks."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/extensions/vueNodes/layout/useNodeDrag.test.ts` at line 114,
Update the isLGraphNode stub used by the startDrag tests to distinguish actual
LGraphNode instances, preferably using the real LiteGraph class or shared
factory instead of a constant mock. Add a selected LGraphNode to the relevant
selectedItems case and assert it is excluded from nonNodeStartPositions,
ensuring the !isLGraphNode filter cannot be inverted or omitted without failing.

Source: Path instructions

@christian-byrne

christian-byrne commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Corrected 2026-08-22 — this is NOT a merge blocker. My original reachability argument was wrong.
I have rewritten this comment in place so nobody reads the stale version while deciding Monday's merge.

Original claim: loading a saved workflow with a connected rgthree Power Prompt deletes its output
wiring. That is false. The pack writes app.graph.links[link].origin_slot = 3 on the line after
the discarded push, and that write is honoured — validateEndpointUpdates has no origin-side check,
so an origin-only patch passes. Slot 0 is already empty when links = null runs, and nothing is
disconnected. The pack works.

What is real is the underlying asymmetry, and it is worth fixing before this API reaches more
packs. NodeOutputSlot.commitLegacyLinks() iterates only links that already exist and disconnects
those not desired; there is no create path, so additions cannot land while removals always do.

The genuine victim is rgthree-comfy/web/common/link_fixer.js — the "Fix bad links" repair tool:

node.outputs[slot].links.push(linkId)           // repair arm  -> DISCARDED
node.outputs[slot].links.splice(linkIdIndex, 1) // destroy arm -> HONOURED

A repair tool that silently repairs nothing while its delete path still works is a bad failure
direction — the user runs it believing they are fixing things. Menu-triggered, not load-triggered,
so lower severity than I claimed.

Worth noting for the fix decision: ADR 0008:197 promises the safe endpoint ("assignments are
ignored"). Making the setter fully inert both removes the hazard and restores the documented
contract. See #15618 for the seven documents stating that contract, and #15634 for the
inert-or-honoured umbrella rule.

Corpus reachability, live control (/registerExtension/ -> 84 files over 157 files / 29 packs):
1 file / 1 site / 1 pack for the assignment idiom.

@christian-byrne christian-byrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

C3 review — layout / geometry / spatial state. Reviewed at 5002fae1b12d44831a21367afa7c0f798f7e7a2c against merge base 6532665db947acb61ed044fe91a1d4fe1fb84c8b.

There is no pos/size analogue of the NodeOutputSlot asymmetry behind #15620. I went looking for a half-commit and did not find one. All ten mutation idioms I could construct commit to layoutStore, and reads re-synchronise from it. Verified by execution, 20/21 assertions, both control arms live:

idiom result
node.pos = [x,y] / node.size = [w,h] commits
node.pos[0] = x / node.size[0] = w commits
node.pos.set([x,y]) / .fill(v) / += commits
Object.assign(node, { pos }) commits
read after an external store move, no prior read reflects the store
Reroute.pos whole-array and in-place both commit

In-place element mutation and whole-array assignment were tested separately. They do not differ. createMutationView calls synchronize() before capturing the previous value on every trap, so a write always lands on a fresh base — that generality is why this surface has no gaps where the hand-enumerated slot views did.

Corpus check, 157 frontend files / 29 packs, control arm /registerExtension/ live at 84 files: 68 call sites across 6 packs mutate node geometry (.pos[i] = 13 sites, .size[i] = 18, .pos =/.size = 8, setPos/setSize 29). All of them work.

ADR 0003's own 2026-08-04 decision is implemented, which nothing on the branch says out loud. Verified 7/7 with a live control: reportContentSize does not change node.size and does not leak into serialize(); renderingSize is max(requested, content); collapsing does not overwrite requested size. The three defects that amendment lists as motivation are fixed on the serialize axis.

Three defect hypotheses I formed while reading died under execution, and I am withdrawing them rather than softening them:

  1. "Reroute layouts leak across clearGraph" — wrong. projectReroute (layoutStore.ts:1218) cleans rerouteLayouts and the spatial index off the ydoc observer. Probe confirms queryRerouteAtPoint returns null after clearGraph, with a live pre-clear control.
  2. "LGraphNode.move() and snapToGrid() read a raw unsynchronised _pos" — wrong. Both call refreshGeometry() on the immediately preceding line (LGraphNode.ts:2310, :3634). My grep showed the read and not its predecessor.
  3. "The minimap digest mixes a nodeGeometryVersion that does not exist" — wrong, it is at layoutStore.ts:248.

One thing worth calling out as a strength, because it is load-bearing and untested: the clear() ordering. teardownOwnedGraphs passes removeLayouts: !owner.isRootGraph so a root graph's entries survive the detach, and resetAfterClear removes them via layoutStore.clearGraph(graphId) before reassigning this.id. Reverse those two and every workflow reload inherits the previous session's node positions. Nothing pins that order — suggested as a regression test, not a change request.

Three inline comments below. Filed separately: #15623 (document the mirror contract), #15624, #15625, #15626, plus two pre-existing ones I verified against the merge base and am not attributing to this PR: #15627, #15628.

Full writeup, denominators and the withdrawn-claims section: research/review/c3-layout-geometry.md in the ECS review workspace.

Not run and not cited: pnpm lint (eslint reports nothing on src/lib/litegraph/, where most of this slice lives, so an eslint-clean claim there would be vacuous), pnpm typecheck, and Playwright. rendererToggleGeometry.spec.ts and resize.spec.ts changed here and are unexercised by me.

commit()
}

return new Proxy(target, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

createMutationView is the reason node.pos[0] = x still works after geometry moved into the CRDT store, and it is the best-built mirror on the branch — trapping set, deleteProperty and defineProperty as well as get, and calling synchronize() before snapshotting the previous value so writes land on a fresh base. That generality is why I could not find a half-commit here.

It is also documented nowhere: git grep -c createMutationView 5002fae1b1 -- docs returns zero files. Five other ECS concerns got a per-concern doc on this branch; layout got none, and ADR 0003 carries the reasoning without ever naming this mechanism.

That matters for one row an extension author cannot guess: {...node} drops pos and size (prototype accessors) while keeping _pos/_size/_posSize — the same shape as #15594. I checked reachability and found none: 0 of 29 packs spread a node (control live at 84/157 files), and of 13 candidate spread sites in src/ against a 238-spread control, none spreads an LGraphNode.

Filed as #15623. Not blocking.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The code is the documentation 😛

if (nodeAttachments.has(node)) detachNodeLayout(node)

const graphId = graph.rootGraph.id
if (layoutStore.getNodeLayout(graphId, node.id)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This branch adopts the store's entry and discards the node's own pos/size, silently. adoptNodeAttachment below does readNodeRect(graphId, node.id, node._posSize), writing the store rect straight over _pos/_size.

Verified: seed a store entry at (-500,-600,11,12), then graph.add(node) with node geometry (10,20,200,100) — both end at the store's values.

This is the only place on the geometry surface where a node-vs-store disagreement resolves store-first, and it reads like a fast path rather than a precedence rule.

I could not construct a production path that reaches it, so this is a comment-or-assert ask, not a bug report. The guards: remove deletes via detachNodeLayout (LGraph.ts:1313), root clear() deletes via clearGraph (:582), subgraph release deletes via detachGraphLayouts (:1306), and replacement goes through transferLayoutAttachment, which adopts deliberately. Undo/redo is snapshot-based through configure, which clears first — reasoning, not something I ran.

Filed as #15624.

size: [number, number]
}>
): void {
clearViewGeometry(): void {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

clearViewGeometry() clears nodeChangeListeners two lines below, which drops every onNodeChange subscription without running any of the unsubscribe closures it handed out. Consumers keep a live-looking stop-closure and go deaf. This runs on every renderer toggle (GraphCanvas.vue:272, :281).

Verified — and the control needed repairing first, which is worth flagging: layoutStore dispatches through queueMicrotask (:1269), so nothing fires synchronously and a synchronous "listener did not fire" assertion passes for the wrong reason. With await setTimeout(0), the control fires and the post-clear case does not.

Latent, not live: onNodeChange has zero non-test consumers at this SHA — the only layoutStore subscribe method without one. onChange and onGeometryChange are both used by notifyLayoutChanges.ts and both are correctly released on unmount and on the mid-life renderer toggle.

So the ask is to pick one now rather than later: leave nodeChangeListeners alone here (a subscription is not view-scoped data), or delete onNodeChange as dead API. Either is fine; the failure mode for the next person to add a consumer only reproduces after a renderer toggle.

Filed as #15625.

christian-byrne and others added 2 commits August 21, 2026 22:36
…path (#15595)

## Summary

comfyui-promptchain does not break through `slotLinks.ts:192`. Its break
is a read divergence: `{...slot}` cannot carry `link` because `link` is
a prototype accessor, so the pack's own later reads of `slot.link`
return `undefined` and it acts on "nothing is connected". This pins that
mechanism, and pins the `:192` early return separately so the two stop
being conflated.

Tests only. No production change.

## Changes

- **What**: five tests in `legacySlotLinkMutations.test.ts`, replaying
the two functions the pack actually runs —
`mobcat40/ComfyUI-PromptChain` `js/lib/order-chain.js:123`
(`updateInputLabels`, the `node.inputs[i] = {...slot}` site) and
`js/main.js:507` (`trimEmptyAutogrowSlots`, a raw `inputs.splice` gated
on `input.link != null`).

Three pass, two are `it.fails` asserting the wanted behaviour:

| Test | Kind | Pins |
| --- | --- | --- |
| `keeps the link store correct when the pack replaces every slot` |
pass | The indexed overwrite does not corrupt the store. `removeInput`
after it reindexes correctly. |
| `never rejects the endpoint batch, though a rejection is detectable` |
pass | Two arms. Real promptchain sequence: 0 rejections. Forced
rejection: 1, and the node keeps its old input layout while
`removeInput` returns normally — #15593's claim, asserted. |
| `reads the live link id back through a spread copy` | `it.fails` |
`copy.link` should return the live id. Returns `undefined`. |
| `keeps connected inputs when the pack re-reads slot.link` | `it.fails`
| After the pack's label pass, its own trim reads `.link` as `undefined`
on every slot, computes `keepCount = 1`, and splices off 3 of 4 inputs
including all 3 connected ones. |
| `keeps every input when the same trim reads live slots` | pass |
Control for the row above. Same fixture, same trim, live class slots:
removes 0. |

The control is the point: 3 slots destroyed vs 0, same function, same
fixture, one arm known non-zero.

`graph.serialize()` after that trim still lists all three links
targeting input slots the node no longer has, so this reaches the saved
workflow.

## Review Focus

**The `:192` early return is real but is not promptchain's bug.** Across
every sequence tried — reentrant slot substitution from
`onConnectionsChange` during `removeInput`, raw `inputs.splice` followed
by `removeInput`, and reconnecting after both — `updateEndpoints`
returned `ok` every time and `console.error('Failed to replace node
inputs')` fired 0 times. It stays worth fixing on its own merits; it is
not the pack break.

**Consequence for the shim.** A shim that intercepts the write and
routes `link`/`links` mutations does not help here, because promptchain
never writes `link` — it reads it. Mutation M1 below simulates a shim
that rehydrates the assigned plain object into a `NodeInputSlot`; that
one does fix it.

## Mutation verification

Baseline for this file: **8 passed | 6 expected fail (14)**.

| Mutation | Result |
| --- | --- |
| M1 — rehydrate plain objects into `NodeInputSlot` on indexed
assignment to `node.inputs` (a shim simulation) | **3 failed \| 8 passed
\| 3 expected fail**. All three failures are `it.fails` markers flipping
to pass: both new ones plus the pre-existing `disconnects through a
plain-object input slot`. Widened to `src/lib/litegraph/src/node/` +
`linkStore.test.ts` + `LLink.store.test.ts`: **3 failed \| 124 passed \|
3 expected fail (130)** — same three markers, no genuine regression. |
| M2 — drop the `return []` at `slotLinks.ts:194` so the rejection falls
through | **1 failed \| 7 passed \| 6 expected fail** — `never rejects
the endpoint batch`, on the layout assertion. |
| M3 — drop `removals` from the endpoint batch in `replaceNodeInputs` |
**1 failed \| 7 passed \| 6 expected fail** — `keeps the link store
correct`. |

Each reverted; baseline restored and re-run green after each.

Both `it.fails` markers were checked by flipping them to `it`: both fail
on `AssertionError`, never on a `TypeError`. The first draft of one of
them passed on `TypeError: source.getOutputLinks is not a function` and
was rewritten.

## Gates

`oxfmt --check`, `oxlint --type-aware` (verified non-vacuous against a
deliberately bad file: exit 1), `knip` exit 0, `vue-tsc --noEmit` at
8GB: 0 errors on any `src/lib/litegraph/` path. The 1418 `TS2688` plus
68 implicit-any errors it reports are the known symlinked-`node_modules`
artifact, all in unrelated `.vue` files.

- Refs #15593

---------

Co-authored-by: Amp <amp@ampcode.com>
## Summary

`{ ...graph.links[id] }` loses every topology field on this branch, and
there was no test for it. This adds the `LLink` half of the failure
class `node/legacySlotLinkMutations.test.ts` already pins for slots.

## Changes

- **What**: `src/lib/litegraph/src/LLink.spreadCopy.test.ts` — two
`it.fails` cases asserting the behaviour we want, so they flip green
when a fix lands.
1. A spread copy of a link carries `id`, `type`, `origin_id`,
`origin_slot`, `target_id`, `target_slot`.
2. The realistic consequence: snapshot outgoing links as spread copies,
`disconnectOutput`, reconnect the far ends from the copies, and the
downstream nodes are still wired.

`LLink` went from 16 own-field declarations at merge-base `6532665db9`
to 12 at head, with the six topology fields becoming prototype accessors
over `_state` and zero `defineProperty` calls. Prototype accessors are
not own properties, so spread copies nothing.

Case 2 is the shape ComfyUI-Custom-Scripts uses in "Add Clip Skip" and
"Add LoRA" (`web/js/quickNodes.js:132`, `:156-157`): the user's
downstream wiring is disconnected and never restored. Filed as #15594.

Only case 1 proves the mechanism. Case 2 is what a user reports.

## Review Focus

**Mutation numbers.** Scratch edit defining the six fields as own
enumerable accessors in the `LLink` constructor, then reverted:

| run | test files | tests |
| --- | --- | --- |
| baseline | 85 passed | 1261 passed, 6 expected fail |
| mutated | 1 failed, 84 passed | 2 failed, 1261 passed, 4 expected fail
|
| reverted | 85 passed | 1261 passed, 6 expected fail |

The 2 failures are exactly the 2 new cases, failing with `Expect test to
fail`. Nothing else moved.

**Corpus: 1 of 29 local packs, 3 call sites — all in
ComfyUI-Custom-Scripts.** Measured over 157 frontend pack files, control
`/registerExtension/` → 84 files. Widened to `Object.assign({}, link)`,
`structuredClone`, and `JSON.parse(JSON.stringify(...))` on link-named
identifiers: 0 additional. This bounds #15594 rather than broadening it
— the idiom is rare, but the one pack using it is widely installed.

**`vue-tsc` already knows.** The first draft typed the copies as the
spread's inferred type and produced 8 `TS2339` errors — TypeScript
models the accessors as dropped. The committed version uses
`Partial<LLink>`, which is what a JS caller actually holds. Typecheck at
8GB: 0 errors in the new file (1494 → 1486 total, the 8 removed are
mine).

The file carries `// oxlint-disable no-misused-spread` — that rule fires
on exactly the pattern under test.

Refs #15594

---------

Co-authored-by: Amp <amp@ampcode.com>
christian-byrne and others added 3 commits August 21, 2026 22:47
Both #15577 and #15581 reproduce on this branch, and #15577 is reachable
through files today's shipping frontend already saves. Seven `it.fails`
assertions state the wanted behaviour; ten controls keep them from
passing for the wrong reason.

## #15577 — reachable, and it re-wires the node

`normalizeConfiguredTopology` keys duplicates on `target_id:target_slot`
and keeps `data.links[first]` rather than the link `input.link` names.
With two links into one input from different origins, the node comes up
fed by a different upstream node than the file names, and nothing is
logged.

Reachability, measured rather than argued. Driving rgthree
`link_fixer`'s mirror-only creation idiom (`output.links.push(id)` +
`input.link = id`, no `connect()`) on `main` at `32d0b6e202` and
serializing produces:

```
"links":[[1,1,0,3,0,"number"],[2,2,0,3,0,"number"]]
"inputs":[{"name":"input_0","type":"number","link":2}]
```

That is `conflictingOriginLinksRoot` byte for byte. The shape is not
hypothetical and it persists across every save on `main`.

Same fixture, both branches:

| | `main` `32d0b6e202` | this branch `9f6ab9adda` |
| --- | --- | --- |
| `getInputLink(0).origin_id` | 2, the origin `input.link` names | 1 |
| links at `3:0` | 2 | 1 |

Dropping to one link is correct, the store enforces one link per input.
Which one survives is the regression.

This branch closes the producer: the `input.link` setter ignores id
assignment and the `output.links` view discards additions, so the same
idiom now serializes one link. Pinned by `discards a link written only
through the legacy slot mirrors`. Forward-safe, but files saved before
the branch ships still load wrong.

Mutation: preferring the `input.link`-referenced survivor and warning on
the drop flips both `it.fails` to pass, controls unchanged.

The negative control `test-cases.md` specified does not exist.
Re-narrowing the key to the 4-tuple was predicted to leave two links
registered and fail the degeneracy control. It does not: the store
rejects the second link downstream and the terminal graph state is
identical, link count and all. The 4-tuple key and the 2-tuple key are
indistinguishable by any assertion on the resulting graph for this
fixture. The control that remains proves the fixture is non-degenerate,
which is what it is for.

## #15581 — reproduces, and the issue's suggested fallback does not fix
it

A serialized input whose name has no live counterpart is skipped, keeps
its stale `target_slot`, and the resulting `occupied-target` rejection
`break`s the loop, abandoning every remaining move for that node.

Three cases in `LGraph.inputSlotRealign.test.ts`: `configure` drops an
input, `configure` renames one, and `realignInputLinkSlots` called
directly on a pre-seeded incumbent. Net effect in cases 1 and 2 is that
`in_a` reports `in_c`'s link and `in_b` reports `in_a`'s. No
`has_errors`, `console.error` only.

Two mutations, and they disagree:

| Mutation | `it.fails` flipped | Controls |
| --- | --- | --- |
| Per-link retry after a rejected batch, the issue's fallback | 1 of 4 |
8 green |
| Blocking incumbents passed to `updateEndpoints`'s `removals` | 4 of 4
| 8 green |

The per-link fallback fails because the collision cascades: link 3
squats slot 0, so link 1 cannot move off slot 1, so link 2 cannot move
onto it. Retrying one at a time hits the same wall. Only evicting the
stale incumbent clears it. Worth deciding before anyone implements the
issue's first suggestion.

## Counts

17 tests across the two files: 10 pass, 7 expected fail. The eight
pre-existing `#3348` tests stay green under both mutations, which is the
negative control that the fix does not break what #3348 was filed for.

`oxfmt` and `oxlint --type-aware` clean. `vue-tsc --noEmit` clean for
the three files in this diff; the repo-wide count is not meaningful from
this worktree, see below.

On typecheck, because it will bite the next person working in a worktree
with a borrowed `node_modules`. The first two pushes carried 10 real
type errors that `vitest`, `oxfmt` and `oxlint --type-aware` all passed:
nine branded-id mismatches (`TS2339`, `TS2322` on `LinkId`) and one
wrong import path (`TS2459`). Fixed in `4651215809` and `20039d9e42`.

They got through because my local typecheck was lying, in two different
ways:

1. `vue-tsc` **OOMs at the 2GB default heap** — `FATAL ERROR:
Ineffective mark-compacts near heap limit`, exit 134, after 18 lines.
Filtered for errors that reads as zero. Silence from a dead process is
indistinguishable from a pass.
2. The phantom errors are **not only `TS2688`**. With
`NODE_OPTIONS=--max-old-space-size=8192` the run completes at exit 2
with 1418 `TS2688` plus 68 `TS7006`/`TS7031`/`TS7053` implicit-any
errors in `.vue` and `.stories.ts` — downstream fallout of the broken
type roots, not real.

Working recipe: larger heap, confirm exit 2 rather than 134, then grep
for your own changed files. Zero for all three files here.

## Wider class, filed separately

All three `updateEndpoints` call sites on this branch swallow rejection
into `console.error` and degrade silently, none marks `has_errors`, none
uses the repo's unified `reportError`. `slotLinks.ts:192` is the worst
of them: on rejection it returns `[]` before `node.inputs.splice`, so
the node keeps its old input layout and the empty return is
indistinguishable from "nothing to replace". Follow-ups tracked in the
workspace todo.

- Repros #15577
- Repros #15581

---------

Co-authored-by: Amp <amp@ampcode.com>
@christian-byrne

christian-byrne commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Corrected 2026-08-23 — this PR is GREEN and none of this blocks Monday. Rewriting in place so
nobody reads the stale version. My original comment was right about the symptom and wrong about
the cause
, and one paragraph described main while claiming to describe this branch.

Current state: head 907ca2b14781 check-runs, 70 success, 11 skipped, 0 failure. test
and codecov/project both pass, and all ruleset-required checks pass. 81f4bd28c7 fixed the test
about two hours after the commit I flagged.

What actually caused the red, by bisect over 21 commits with the test file pinned to known-good
content so it measured product changes rather than test edits:

first bad commit: 13a302eadd  refactor!: migrate entity state to dedicated stores

Not 08f7232d4b as I suggested — 08f7232d4b^ fails identically. It is neither a regression nor
a test-setup artifact but a semantic merge conflict
: #15568 was written against main's badge
mechanism and merged onto a branch that had already replaced it. useNodeBadge.ts goes 224 → 60
lines, swapping node.badges.push(...) for registerBadgeRowsProvider(nodeBadges); nothing in
branch src/ writes node.badges (5 production sites on main, 0 here). The extension API is
intact and rendering, because LGraphNode.drawBadges composes both badgeDrawObjects(this, badgeRows(this)) and this.badges.

Correction to my NodeState paragraph. I wrote that VueNodeData declares badges?: Badges and
that useGraphNodeManager populates it. That is true of main, not this branch — the
VueNodeData type no longer exists here (21 files reference it on main, 3 on this branch, and
all 3 are docs/architecture/*.md). NodeState is correctly badge-free: usePartitionedBadges
never reads nodeData.badges; it resolves the live node by id and reads nodeBadges(node) for
core/pricing plus node.badges for extensions. Badges have a home, and my concern there was
unfounded.

The branch also improves parity — hides built-in Vue badges is it.fails on main and a
passing it here (#15567).

One genuine defect did fall out, filed as #15662: computeBadges emits lifecycle, id, source,
the canvas re-sorts via CORE_JOIN_ORDER = ['id','lifecycle','source'], and usePartitionedBadges
never applies it — Vue renders BETA #1 where the canvas renders #1 BETA. Pre-existing on
main, not introduced by this PR.
OSS browser defaults to classic canvas and is unaffected; Cloud
and Desktop default Vue nodes on, so they see it today. Cosmetic.

Apologies for the noise on a green PR — the symptom was real when I posted it, the attribution was
not.

ampagent and others added 3 commits August 22, 2026 07:50
#15596)

`WidgetValue` and `NodeProperty` both admit `null`, `setProperty(name,
null)` propagates it to the bound widget, and `LGraphNode.serialize`
actively mints it — `val ?? null` turns an `undefined` widget value into
`null` on the way into `widgets_values`. So `null` is already in
first-party workflow JSON with no extension involved.

Nothing asserted that any persistence path round-trips it, and on `main`
one path does not: `SubgraphNode.serialize` filters through
`isWidgetValue`, whose `main` predicate rejects `null`, so a null
promoted value becomes `undefined` and — when every promoted widget is
null — the whole `widgets_values` key is deleted. This branch already
fixes the behaviour by widening `isWidgetValue` to `value == null`. This
PR adds the assertions that were missing on both sides.

I enumerated every persistence path that filters widget values, not just
the subgraph one. Verdicts:

| Path | Filter | Round-trips null? |
| --- | --- | --- |
| `LGraphNode.serialize` | `widget.serialize === false`; `val ?? null` |
yes — and mints null from undefined |
| `LGraphNode.configure` (indexed + named-values) | `serialize ===
false`, `name in namedValues` | yes |
| `SubgraphNode.serialize` | `isWidgetValue` | yes on this branch, **no
on `main`** |
| `SubgraphNode._applyPromotedWidgetValues` | `value !== undefined` |
yes |
| `executionUtil` API prompt | `widget.options?.serialize === false` |
yes |
| draft / autosave | none of its own | transparent |
| `workflowSchema` load validation | `z.any()` | yes |
| clipboard `_serializeItems` | none of its own | transparent |
| `proxyWidgetMigration` | `isWidgetValue` | yes on this branch, **no on
`main`** (null classed as a hole) |

`isWidgetValue` has two call sites, not one — the same predicate change
fixes both.

New coverage, all mutation-verified (mutating the corresponding filter
to drop null fails the test; unmutated control passes):

- `widgetValueNullContract.test.ts` — workflow write, workflow read via
both restore branches, draft-cache JSON text, two-pass idempotence, plus
a `widget.serialize === false` control arm and a stored-null-vs-absent
control arm
- `executionUtil.test.ts` — prompt path emits null, with an
`options.serialize` control arm
- `workflowSchema.test.ts` — array and object forms preserve null

One trap worth flagging for reviewers: widget state is keyed
`graphId:nodeId:name` and `configure()` restores the original graph id,
so a naive save/load test re-adopts the live `widgetValueStore` entry
and passes **without reading the serialized JSON at all**. My first
draft did exactly that. `roundTripGraph` drops the store between save
and load; without that line four of these tests are vacuous.

ADR 0016 records the contract and the evidence, including the
reachability measurement (nine public extension repos assign null to a
widget value, against a 4,280-file control arm) and the blast radius of
the alternative (removing `null` from `WidgetValue` yields 19 `vue-tsc`
errors across 11 files, against a 0-error control).

Gates run by hand — worktrees run no hooks: `oxfmt`, `oxlint
--type-aware` (clean, verified against a control that emits), `vue-tsc
--noEmit` exit 0 / 0 errors, 52 tests passing.

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Amp <amp@ampcode.com>
## Summary

Add behavioral coverage for the remaining ECS migration bridge risks and
update the migration audit to reflect the new evidence.

## Changes

- **What**: Adds Playwright coverage for promoted-subgraph
delete/undo/redo and geometry across navigation, renderer switching,
history, and reload; adds Vitest regressions for virtual-consumer role
inference and root-scoped, non-creating coach-target layout reads;
updates the migration plan and verification audit with covered and
remaining gaps.

## Review Focus

Confirm the bridge-history scenarios and audit assessments match the
intended ECS authority boundaries. Verification: `pnpm
test:browser:local
browser_tests/tests/vueNodes/layout/ecsBridgeHistory.spec.ts
--repeat-each 2` (4 passed); `pnpm test:unit
src/renderer/extensions/firstRunTour/roles/heuristicRoles.test.ts
src/renderer/extensions/firstRunTour/tour/canvasCoachTarget.test.ts` (60
passed); `pnpm typecheck`; `pnpm typecheck:browser`; targeted ESLint,
oxlint, and oxfmt checks.

Child of #15443.

---------

Co-authored-by: Amp <amp@ampcode.com>
@christian-byrne

Copy link
Copy Markdown
Contributor

Root cause: not a badge regression, and the PR is green now. It does not block Monday.

Following up on my comment above, which I need to correct in two places.

1. The head moved and the failure is already fixed. I measured 08f7232d4b; the branch is now at
907ca2b147. At 2026-08-23T01:51:12Z, paginated: 81 check-runs — 70 success, 11 skipped, 0 failure.
test and codecov/project are both success. 81f4bd28c7 ("test: align badge parity coverage with ECS")
fixed it.

2. 08f7232d4b was not the cause. I implied it was. 08f7232d4b^ fails identically — 6 failed, 3
expected fail. git bisect over the 21 commits from the merge base, with the test file pinned to its
known-good content so the bisect measured product changes rather than test churn:

# first bad commit: [13a302eadd] refactor!: migrate entity state to dedicated stores
commit result
a2603c59a6 merge base 5 passed, 4 expected fail
8616dc131f where it landed (#15568) 5 passed, 4 expected fail
13a302eadd first bad fail
08f7232d4b^ 6 failed, 3 expected fail
08f7232d4b 6 failed, 3 expected fail
81f4bd28c7 fix 6 passed, 3 expected fail
907ca2b147 head 6 passed, 3 expected fail

Why it went red. node.badges really is empty of core badges on this branch, deliberately.
useNodeBadge.ts goes from 224 lines to 60: on main it does node.badges.push(() => badge.value),
here its init() calls installNodeBadges()registerBadgeRowsProvider(nodeBadges). Nothing in
branch src/ writes to node.badges any more (5 production sites on main, 0 here).

Badges still reach the canvas — both sets, composed in LGraphNode.drawBadges:

const badgeInstances = [
  ...badgeDrawObjects(this, badgeRows(this)),  // derived core/credits rows
  ...this.badges.map(...)                       // extension badges, API unchanged
]

node.badges is now reserved for third-party extensions and is still declared, drawn, and read by the
Vue path. So this was a test asserting through a mechanism the refactor replaced — a semantic merge
conflict between #15568 (written against main) and 13a302eadd — not a registration regression.

I checked the fix is not vacuous, since the new ?? '' fallback is the shape that hides a dead test.
Injecting return [] into nodeBadges fails exactly the three positive legacy assertions, counts
unchanged, green on revert. It is a real detector.

The branch improves parity by one case: hides built-in Vue badges is it.fails on main and a
passing it here. That is #15567.


On NodeState having no badges field — that is correct, and badges do have a home.

usePartitionedBadges(nodeData: NodeState) never reads nodeData.badges. It resolves the live node
by id:

42:  const node = rootGraph ? resolveNode(nodeData.id, rootGraph) : undefined
43:  for (const row of node ? nodeBadges(node) : [])          // core + pricing
54:  for (const badge of (node?.badges ?? []).map(toValue))    // extension badges

resolveNode returns LGraphNode | undefined, so node?.badges is a real access on the class
instance — no cast, no optional field. It typechecks. 08f7232d4b dropping badges: node.badges from
the Vue arm's literal removed dead data; the composable never read it from the state object. My earlier
framing compared against VueNodeData, which no longer exists on this branch at all (0 files here, 21
on main), so that comparison was stale.


One real defect, pre-existing and not a blocker. The 3 remaining it.fails are a single ordering
bug. Flipping them to it:

Expected: "#1 BETA my_pack"
Received: "BETA #1 my_pack"

computeBadges emits core rows as lifecycle, id, source. The canvas re-sorts them through
CORE_JOIN_ORDER = ['id', 'lifecycle', 'source'] in joinedCoreText. usePartitionedBadges iterates
in emission order and never applies CORE_JOIN_ORDER. Fix is to sort core by CORE_JOIN_ORDER there.

Scope: Comfy.VueNodes.Enabled is defaultValue: false but
defaultsByInstallVersion: { '1.41.0': isCloud || isDesktop } — so OSS browser is unaffected, and
Cloud and Desktop show BETA #1 today. Cosmetic, pre-existing, correctly marked it.fails.

Two things I did not file as blockers but that are worth a look:

  • If canvasStore.currentGraph is null or the id does not resolve, usePartitionedBadges returns zero
    core and zero extension badges silently. That is exactly why the Vue arm returned '' at
    08f7232d4b — the fix had to add useCanvasStore().currentGraph = graph to the harness.
  • LGraphNode.badges is a plain class field, not part of the reactive NodeState proxy, so an
    extension mutating node.badges after mount will not repaint in Vue-nodes mode, whereas the canvas
    re-evaluates thunks every frame. Reasoned from the dependency graph, not observed at runtime.

Related: #15619, #15567.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview-gpu Creates a preview ephemeral environment for this PR (GPU available) refactor retain-preview Keep the ephemeral preview environment from being torn down for inactivity risk:R3 PR risk grade (advisory shadow check; grader-owned) size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants