[backport core/1.48] fix: preserve subgraph node size when editing or promoting widgets (FE-853) - #14491
Conversation
…E-853) (#14461) *PR Created by the Glary-Bot Agent* --- Single unified PR for FE-853 — the fix plus full regression coverage, targeting `main`. **Supersedes #14374, #14415 and #14418, all of which can be closed.** This branch contains the fix originally proposed in #14374 (authorship preserved) with the regression test from #14415 folded in, rebased onto current `main`. - Fixes [FE-853](https://linear.app/comfyorg/issue/FE-853/bug-promotingunpromoting-a-widget-collapses-subgraph-to-minimum-size) ## The bug Resize a subgraph node, then touch the widget-editing UI, and the node snaps back to its computed minimum size — silently discarding the size you set. Reported in #bug-dump via the "Edit Subgraph Widgets" button; FE-853 tracks the same collapse when promoting or un-promoting a widget. Measured against `main` before the fix: | Step | Node size | | --- | --- | | After the user's resize drag | **506 × 339** | | After clicking "Edit Subgraph Widgets" | **225 × 58** | | After promoting an interior widget | **225 × 58** | ## The fix Three call sites refreshed a subgraph host node's rendering with `computeSize(node.size)`. `computeSize` returns the *minimum* size for the node's content and those calls assigned it back, so every refresh clamped the node down and destroyed any larger user-set size. Replaced with `expandToFitContent()`, which grows the node when content requires more room but never shrinks it: - `src/components/rightSidePanel/subgraph/SubgraphEditor.vue` — `refreshPromotedWidgetRendering()`, the path the "Edit Subgraph Widgets" button hits - `src/core/graph/subgraph/promotionUtils.ts` — `refreshPromotedWidgetRendering()` (promote/demote) and `promoteRecommendedWidgets()` ## Regression coverage **`subgraphEditWidgetsResize.spec.ts`** (2 tests) — approaches the bug from the user-reported entry point: 1. Clicking the "Edit Subgraph Widgets" selection-toolbox button, which is what the screen recording shows 2. Promoting an interior widget, the trigger in FE-853's title **`subgraphResizePreservation.spec.ts`** (6 tests) — promote, un-promote, panel open, multi-widget sequences, resize-still-works-after-promotion, and a nested-subgraph host. **Unit tests** (60) across `promotionUtils.test.ts` and `SubgraphEditor.test.ts`. Sizes are read from the graph model rather than DOM bounding boxes, so assertions aren't confounded by canvas zoom or by the canvas shrinking when the side panel opens. The promotion test asserts `steps` actually reached the host node before checking size, so it can't pass vacuously if promotion silently became a no-op. Supporting fixtures: `NodeOperationsHelper.growNodeByDrag()` (user-style resize drag returning model-space size, beside the existing canvas-based `resizeNode`) and `SubgraphEditor.openFromSelectionToolbox()` (toolbox-button path, complementing the context-menu `ensureOpen()`). ## Verification The tests were confirmed to detect the regression rather than pass incidentally, by reverting the fix in place on this branch: | `src` state | `subgraphEditWidgetsResize` | | --- | --- | | `expandToFitContent()` | **2 passed** | | reverted to `computeSize(node.size)` | **2 failed** — `expected 506.0625, received 225` | In the reverted run the failing assertion is the width check, not the promotion guard. Against current `main` after rebase: **8 e2e passed**, **60 unit passed**, and `pnpm typecheck`, `pnpm typecheck:browser`, ESLint, oxlint, oxfmt `--check` and `pnpm knip` all clean. ## Review history carried over - @DrJKL (#14415): `growNodeByDrag` belongs outside `SubgraphHelper` → it lives in `NodeOperationsHelper`; `SubgraphHelper` is unchanged apart from #14374's own edit. - CodeRabbit (#14415): promotion test could pass vacuously → added the `getPromotedWidgetOrder()` guard. CodeRabbit approved #14415 and #14418. ## Screenshots   --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
📄 Knowledge reviewDosu skipped reviewing this PR because your organization has used its |
🎨 Storybook: ✅ Built — View Storybook🎭 Playwright: ✅ 1761 passed, 0 failed · 1 flaky📊 Browser Reports
📦 Bundle Size
⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-07-31T17:31:33.817Z",
"gitSha": "c977a5007041b422c496743d954c6f4476aa30f0",
"branch": "backport-14461-to-core-1.48",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2202.150999999986,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.272000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 654.014,
"heapDeltaBytes": -22609716,
"heapUsedBytes": 45537776,
"domNodes": -274,
"jsHeapTotalBytes": 19656704,
"scriptDurationMs": 21.546,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-idle",
"durationMs": 2071.8070000000353,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.722000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 517.3180000000001,
"heapDeltaBytes": -20471132,
"heapUsedBytes": 47405196,
"domNodes": -275,
"jsHeapTotalBytes": 20180992,
"scriptDurationMs": 15.757,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1872.8449999999839,
"styleRecalcs": 74,
"styleRecalcDurationMs": 37.058,
"layouts": 12,
"layoutDurationMs": 3.676,
"taskDurationMs": 874.2620000000001,
"heapDeltaBytes": -16422032,
"heapUsedBytes": 51471708,
"domNodes": -274,
"jsHeapTotalBytes": 21229568,
"scriptDurationMs": 115.345,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1843.9789999999903,
"styleRecalcs": 76,
"styleRecalcDurationMs": 38.879999999999995,
"layouts": 12,
"layoutDurationMs": 3.8820000000000006,
"taskDurationMs": 818.867,
"heapDeltaBytes": -1442468,
"heapUsedBytes": 66493132,
"domNodes": 59,
"jsHeapTotalBytes": 21102592,
"scriptDurationMs": 115.06899999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1754.3249999999944,
"styleRecalcs": 32,
"styleRecalcDurationMs": 17.043,
"layouts": 6,
"layoutDurationMs": 0.745,
"taskDurationMs": 371.41,
"heapDeltaBytes": 7285096,
"heapUsedBytes": 75188496,
"domNodes": 78,
"jsHeapTotalBytes": 20578304,
"scriptDurationMs": 18.28,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1719.6290000000545,
"styleRecalcs": 32,
"styleRecalcDurationMs": 16.39,
"layouts": 6,
"layoutDurationMs": 0.5710000000000001,
"taskDurationMs": 365.704,
"heapDeltaBytes": 7065032,
"heapUsedBytes": 74787796,
"domNodes": 79,
"jsHeapTotalBytes": 20054016,
"scriptDurationMs": 18.073000000000004,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 561.505000000011,
"styleRecalcs": 11,
"styleRecalcDurationMs": 7.531999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 363.168,
"heapDeltaBytes": -12031092,
"heapUsedBytes": 55896736,
"domNodes": 18,
"jsHeapTotalBytes": 21102592,
"scriptDurationMs": 53.459999999999994,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "dom-widget-clipping",
"durationMs": 579.2720000000031,
"styleRecalcs": 12,
"styleRecalcDurationMs": 8.848999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 379.406,
"heapDeltaBytes": -12317028,
"heapUsedBytes": 55527500,
"domNodes": 20,
"jsHeapTotalBytes": 20578304,
"scriptDurationMs": 55.079,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2011.2680000000296,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.762999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 658.645,
"heapDeltaBytes": 6728360,
"heapUsedBytes": 65840628,
"domNodes": -278,
"jsHeapTotalBytes": 4521984,
"scriptDurationMs": 90.167,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2033.2799999999907,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.391999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 657.038,
"heapDeltaBytes": 7193812,
"heapUsedBytes": 65905668,
"domNodes": -277,
"jsHeapTotalBytes": 4521984,
"scriptDurationMs": 90.54399999999998,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-pan",
"durationMs": 2137.832000000003,
"styleRecalcs": 67,
"styleRecalcDurationMs": 13.191,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1243.916,
"heapDeltaBytes": 2309700,
"heapUsedBytes": 62624660,
"domNodes": -281,
"jsHeapTotalBytes": 5513216,
"scriptDurationMs": 426.572,
"eventListeners": -142,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-pan",
"durationMs": 2176.4360000000806,
"styleRecalcs": 68,
"styleRecalcDurationMs": 13.621999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1234.53,
"heapDeltaBytes": 354404,
"heapUsedBytes": 61090148,
"domNodes": -275,
"jsHeapTotalBytes": 4726784,
"scriptDurationMs": 400.71399999999994,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-zoom",
"durationMs": 3229.770999999971,
"styleRecalcs": 65,
"styleRecalcDurationMs": 14.992000000000003,
"layouts": 60,
"layoutDurationMs": 8,
"taskDurationMs": 1488.0279999999998,
"heapDeltaBytes": 745012,
"heapUsedBytes": 62629072,
"domNodes": -280,
"jsHeapTotalBytes": 7143424,
"scriptDurationMs": 518.489,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3196.187000000009,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.395,
"layouts": 60,
"layoutDurationMs": 7.866000000000001,
"taskDurationMs": 1491.257,
"heapDeltaBytes": -2088712,
"heapUsedBytes": 59257924,
"domNodes": -279,
"jsHeapTotalBytes": 7405568,
"scriptDurationMs": 516.482,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2033.5380000000214,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.2810000000000015,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 648.417,
"heapDeltaBytes": 7109640,
"heapUsedBytes": 67872444,
"domNodes": -276,
"jsHeapTotalBytes": 4784128,
"scriptDurationMs": 90.67499999999998,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2037.1690000000626,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.514,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 664.1189999999999,
"heapDeltaBytes": 6863988,
"heapUsedBytes": 67108388,
"domNodes": -274,
"jsHeapTotalBytes": 4259840,
"scriptDurationMs": 94.644,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 578.7250000000199,
"styleRecalcs": 46,
"styleRecalcDurationMs": 10.033,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 396.8440000000001,
"heapDeltaBytes": -11512172,
"heapUsedBytes": 56442080,
"domNodes": 18,
"jsHeapTotalBytes": 21364736,
"scriptDurationMs": 123.02200000000002,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 581.0359999999264,
"styleRecalcs": 46,
"styleRecalcDurationMs": 9.665,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 388.728,
"heapDeltaBytes": -12073544,
"heapUsedBytes": 55829952,
"domNodes": 18,
"jsHeapTotalBytes": 21626880,
"scriptDurationMs": 119.31000000000002,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2009.4269999999597,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.491,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 451.665,
"heapDeltaBytes": 3525016,
"heapUsedBytes": 71474724,
"domNodes": 22,
"jsHeapTotalBytes": 20840448,
"scriptDurationMs": 12.721000000000002,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2026.9260000000031,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.457000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 494.813,
"heapDeltaBytes": -22332128,
"heapUsedBytes": 45696940,
"domNodes": -272,
"jsHeapTotalBytes": 20443136,
"scriptDurationMs": 13.438,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1704.5439999999985,
"styleRecalcs": 75,
"styleRecalcDurationMs": 35.929,
"layouts": 16,
"layoutDurationMs": 4.677,
"taskDurationMs": 802.3180000000001,
"heapDeltaBytes": -18963456,
"heapUsedBytes": 48951896,
"domNodes": -275,
"jsHeapTotalBytes": 21229568,
"scriptDurationMs": 91.37299999999999,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1694.7830000000295,
"styleRecalcs": 75,
"styleRecalcDurationMs": 35.786,
"layouts": 16,
"layoutDurationMs": 4.869,
"taskDurationMs": 790.797,
"heapDeltaBytes": -20682376,
"heapUsedBytes": 47181332,
"domNodes": -274,
"jsHeapTotalBytes": 20967424,
"scriptDurationMs": 89.83400000000002,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 1401.1649999999918,
"styleRecalcs": 18,
"styleRecalcDurationMs": 28.595999999999997,
"layouts": 14,
"layoutDurationMs": 11.158000000000001,
"taskDurationMs": 908.6809999999998,
"heapDeltaBytes": 29134312,
"heapUsedBytes": 96695900,
"domNodes": 13673,
"jsHeapTotalBytes": 16252928,
"scriptDurationMs": 31.827999999999996,
"eventListeners": 2371,
"totalBlockingTimeMs": 115,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8233.096999999987,
"styleRecalcs": 250,
"styleRecalcDurationMs": 41.044,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4231.9710000000005,
"heapDeltaBytes": 7728632,
"heapUsedBytes": 66762048,
"domNodes": -275,
"jsHeapTotalBytes": 8134656,
"scriptDurationMs": 1283.116,
"eventListeners": -128,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8185.15699999989,
"styleRecalcs": 250,
"styleRecalcDurationMs": 41.041000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4260.847,
"heapDeltaBytes": 10296328,
"heapUsedBytes": 68729732,
"domNodes": -274,
"jsHeapTotalBytes": 6299648,
"scriptDurationMs": 1303.1819999999998,
"eventListeners": -128,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 17919.038,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17888.64,
"heapDeltaBytes": -52128004,
"heapUsedBytes": 165897696,
"domNodes": -8312,
"jsHeapTotalBytes": -11993088,
"scriptDurationMs": 551.96,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 17599.559999999998,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17586.208,
"heapDeltaBytes": -56667116,
"heapUsedBytes": 167155736,
"domNodes": -8312,
"jsHeapTotalBytes": -12521472,
"scriptDurationMs": 519.7910000000002,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.77333333333336,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 21518.24399999998,
"styleRecalcs": 147,
"styleRecalcDurationMs": 19.18700000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 21484.117,
"heapDeltaBytes": -55196660,
"heapUsedBytes": 153727536,
"domNodes": -8312,
"jsHeapTotalBytes": -16191488,
"scriptDurationMs": 900.807,
"eventListeners": -16381,
"totalBlockingTimeMs": 325,
"frameDurationMs": 18.330000000000048,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 21818.607000000044,
"styleRecalcs": 158,
"styleRecalcDurationMs": 21.042999999999978,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 21794.298000000003,
"heapDeltaBytes": -47724236,
"heapUsedBytes": 176639164,
"domNodes": -8312,
"jsHeapTotalBytes": -15749120,
"scriptDurationMs": 871.6859999999999,
"eventListeners": -16381,
"totalBlockingTimeMs": 495,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 103.21199999998498,
"styleRecalcs": 7,
"styleRecalcDurationMs": 13.443,
"layouts": 3,
"layoutDurationMs": 0.7110000000000001,
"taskDurationMs": 75.452,
"heapDeltaBytes": 3202044,
"heapUsedBytes": 70194332,
"domNodes": 116,
"jsHeapTotalBytes": 3145728,
"scriptDurationMs": 6.264999999999998,
"eventListeners": 33,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 459.53800000006595,
"styleRecalcs": 12,
"styleRecalcDurationMs": 16.871,
"layouts": 2,
"layoutDurationMs": 0.44299999999999984,
"taskDurationMs": 102.168,
"heapDeltaBytes": -16119540,
"heapUsedBytes": 51050344,
"domNodes": 117,
"jsHeapTotalBytes": 7471104,
"scriptDurationMs": 9.528,
"eventListeners": 65,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## core/1.48 #14491 +/- ##
=============================================
- Coverage 79.31% 79.31% -0.01%
=============================================
Files 1746 1746
Lines 99316 99314 -2
Branches 31834 32579 +745
=============================================
- Hits 78776 78774 -2
Misses 20138 20138
Partials 402 402
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Backport of #14461 to
core/1.48Automatically created by backport workflow.