fix(load3d): update renderer pixel ratio on canvas zoom to fix LOD resolution#11734
fix(load3d): update renderer pixel ratio on canvas zoom to fix LOD resolution#11734kaili-yang wants to merge 3 commits intomainfrom
Conversation
…solution Preview 3D and Animation nodes were stuck at the LOD from initial page load because CSS scale3d transforms don't affect clientWidth/clientHeight, so handleResize() always used layout-space dimensions regardless of zoom level. Fix: pass ds.scale as pixelRatio to the renderer so the 3D scene renders at the correct visual resolution when the graph is zoomed in or out. Also fixes captureScene() to save/restore logical size and pixelRatio so exact-pixel captures are unaffected by zoom state.
Cover the three new behaviors introduced by the LOD zoom fix: - handleResize uses getZoomScaleCallback value for setPixelRatio - handleResize falls back to pixelRatio 1 when no callback is set - useLoad3d passes getZoomScale option to createLoad3d - zoom watcher calls handleResize when canvas appScalePercentage changes
📝 WalkthroughWalkthroughAdds zoom-aware rendering: introduces a Changes
Sequence DiagramsequenceDiagram
participant CanvasStore as Canvas Store
participant Composable as useLoad3d
participant Load3d as Load3d
participant Renderer as THREE.WebGLRenderer
CanvasStore->>Composable: appScalePercentage change (watcher)
Composable->>Load3d: handleResize()
Load3d->>Load3d: call getZoomScale() (if provided)
Load3d-->>Renderer: setPixelRatio(zoomScale or 1)
Renderer->>Renderer: adjust internal resolution / render size
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 7✅ Passed checks (7 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🎨 Storybook: ✅ Built — View Storybook |
🎭 Playwright: ❌ 1417 passed, 1 failed · 4 flaky❌ Failed Tests📊 Browser Reports
|
📦 Bundle: 5.23 MB gzip 🔴 +231 BDetailsSummary
Category Glance App Entry Points — 22.5 kB (baseline 22.5 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.24 MB (baseline 1.24 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 77.7 kB (baseline 77.7 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 9 added / 9 removed / 2 unchanged Panels & Settings — 488 kB (baseline 488 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed / 11 unchanged User & Accounts — 17.4 kB (baseline 17.4 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 5 added / 5 removed / 2 unchanged Editors & Dialogs — 113 kB (baseline 113 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 4 added / 4 removed UI Components — 61 kB (baseline 61 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed / 8 unchanged Data & Services — 3.04 MB (baseline 3.04 MB) • 🔴 +289 BStores, services, APIs, and repositories
Status: 13 added / 13 removed / 4 unchanged Utilities & Hooks — 364 kB (baseline 364 kB) • 🔴 +185 BHelpers, composables, and utility bundles
Status: 13 added / 13 removed / 18 unchanged Vendor & Third-Party — 9.88 MB (baseline 9.88 MB) • ⚪ 0 BExternal libraries and shared vendor chunks Status: 16 unchanged Other — 8.83 MB (baseline 8.83 MB) • ⚪ 0 BBundles that do not match a named category
Status: 57 added / 57 removed / 78 unchanged ⚡ Performance Report
No regressions detected. All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-04-29T03:36:34.162Z",
"gitSha": "9a196b02cca75ed9dc617d8a5abe6a8a6ee6614d",
"branch": "fix/load3d-lod-zoom-resolution",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2044.6949999999902,
"styleRecalcs": 9,
"styleRecalcDurationMs": 5.916999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 296.89200000000005,
"heapDeltaBytes": 904680,
"heapUsedBytes": 66848148,
"domNodes": 18,
"jsHeapTotalBytes": 19496960,
"scriptDurationMs": 12.732000000000001,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-idle",
"durationMs": 2007.6510000000098,
"styleRecalcs": 11,
"styleRecalcDurationMs": 7.409999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 335.296,
"heapDeltaBytes": -22889912,
"heapUsedBytes": 43336416,
"domNodes": -257,
"jsHeapTotalBytes": 18837504,
"scriptDurationMs": 19.249,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 1994.0110000000004,
"styleRecalcs": 10,
"styleRecalcDurationMs": 6.360000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 301.088,
"heapDeltaBytes": 1671312,
"heapUsedBytes": 67905756,
"domNodes": 20,
"jsHeapTotalBytes": 19234816,
"scriptDurationMs": 16.462,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1694.0069999999992,
"styleRecalcs": 72,
"styleRecalcDurationMs": 25.077999999999996,
"layouts": 12,
"layoutDurationMs": 2.722,
"taskDurationMs": 597.1099999999999,
"heapDeltaBytes": 4397228,
"heapUsedBytes": 52314096,
"domNodes": -259,
"jsHeapTotalBytes": 16379904,
"scriptDurationMs": 93.25,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1730.517999999961,
"styleRecalcs": 72,
"styleRecalcDurationMs": 27.048,
"layouts": 12,
"layoutDurationMs": 2.723,
"taskDurationMs": 622.81,
"heapDeltaBytes": 10928944,
"heapUsedBytes": 58623632,
"domNodes": -257,
"jsHeapTotalBytes": 15331328,
"scriptDurationMs": 101.67800000000001,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66666666666665,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1707.8599999999824,
"styleRecalcs": 74,
"styleRecalcDurationMs": 31.777,
"layouts": 12,
"layoutDurationMs": 3.111,
"taskDurationMs": 636.7120000000001,
"heapDeltaBytes": -3503764,
"heapUsedBytes": 44880888,
"domNodes": -258,
"jsHeapTotalBytes": 15593472,
"scriptDurationMs": 96.123,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1713.4209999999825,
"styleRecalcs": 31,
"styleRecalcDurationMs": 13.460999999999999,
"layouts": 6,
"layoutDurationMs": 0.5320000000000001,
"taskDurationMs": 236.02,
"heapDeltaBytes": 301512,
"heapUsedBytes": 47699320,
"domNodes": 79,
"jsHeapTotalBytes": 14680064,
"scriptDurationMs": 19.400000000000002,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666682,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1718.939999999975,
"styleRecalcs": 31,
"styleRecalcDurationMs": 13.820999999999998,
"layouts": 6,
"layoutDurationMs": 0.51,
"taskDurationMs": 234.106,
"heapDeltaBytes": 5648404,
"heapUsedBytes": 72064212,
"domNodes": 79,
"jsHeapTotalBytes": 18972672,
"scriptDurationMs": 14.838000000000001,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1719.6919999998954,
"styleRecalcs": 32,
"styleRecalcDurationMs": 14.490000000000002,
"layouts": 6,
"layoutDurationMs": 0.5790000000000001,
"taskDurationMs": 248.78500000000003,
"heapDeltaBytes": 5887628,
"heapUsedBytes": 72096120,
"domNodes": 80,
"jsHeapTotalBytes": 18710528,
"scriptDurationMs": 21.238,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.669999999999998,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 463.2780000000025,
"styleRecalcs": 12,
"styleRecalcDurationMs": 6.657999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 275.849,
"heapDeltaBytes": -12315756,
"heapUsedBytes": 53705592,
"domNodes": 18,
"jsHeapTotalBytes": 20283392,
"scriptDurationMs": 47.023999999999994,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "dom-widget-clipping",
"durationMs": 446.9360000000506,
"styleRecalcs": 12,
"styleRecalcDurationMs": 6.671999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 268.512,
"heapDeltaBytes": -12461512,
"heapUsedBytes": 53678328,
"domNodes": 19,
"jsHeapTotalBytes": 20021248,
"scriptDurationMs": 48.135,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "dom-widget-clipping",
"durationMs": 461.6159999999354,
"styleRecalcs": 12,
"styleRecalcDurationMs": 5.777000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 271.32,
"heapDeltaBytes": -12684520,
"heapUsedBytes": 52761284,
"domNodes": 18,
"jsHeapTotalBytes": 21331968,
"scriptDurationMs": 44.948,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "large-graph-idle",
"durationMs": 2015.9800000000132,
"styleRecalcs": 11,
"styleRecalcDurationMs": 7.195,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 422.76199999999994,
"heapDeltaBytes": 15552844,
"heapUsedBytes": 72167100,
"domNodes": -252,
"jsHeapTotalBytes": -229376,
"scriptDurationMs": 67.368,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2023.631000000023,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.746000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 442.331,
"heapDeltaBytes": 7655704,
"heapUsedBytes": 65684320,
"domNodes": -259,
"jsHeapTotalBytes": -229376,
"scriptDurationMs": 66.50099999999999,
"eventListeners": -125,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2037.319000000025,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 480.36500000000007,
"heapDeltaBytes": 6560712,
"heapUsedBytes": 62787900,
"domNodes": -258,
"jsHeapTotalBytes": -1081344,
"scriptDurationMs": 83.903,
"eventListeners": -125,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2071.1149999999634,
"styleRecalcs": 68,
"styleRecalcDurationMs": 15.878999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 807.345,
"heapDeltaBytes": -1627328,
"heapUsedBytes": 57251336,
"domNodes": -260,
"jsHeapTotalBytes": 6529024,
"scriptDurationMs": 279.45799999999997,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2070.5259999999726,
"styleRecalcs": 68,
"styleRecalcDurationMs": 16.177000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 803.0509999999999,
"heapDeltaBytes": 490368,
"heapUsedBytes": 59509136,
"domNodes": -260,
"jsHeapTotalBytes": 1286144,
"scriptDurationMs": 280.208,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2100.168999999937,
"styleRecalcs": 69,
"styleRecalcDurationMs": 16.365000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 837.646,
"heapDeltaBytes": 413664,
"heapUsedBytes": 59280772,
"domNodes": -260,
"jsHeapTotalBytes": 958464,
"scriptDurationMs": 288.06000000000006,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3087.184000000036,
"styleRecalcs": 66,
"styleRecalcDurationMs": 16.82,
"layouts": 60,
"layoutDurationMs": 6.968,
"taskDurationMs": 1048.729,
"heapDeltaBytes": 8772716,
"heapUsedBytes": 69167680,
"domNodes": -264,
"jsHeapTotalBytes": 4489216,
"scriptDurationMs": 382.493,
"eventListeners": -125,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3037.8299999999854,
"styleRecalcs": 66,
"styleRecalcDurationMs": 16.637,
"layouts": 60,
"layoutDurationMs": 6.739999999999999,
"taskDurationMs": 1012.6409999999998,
"heapDeltaBytes": 8512412,
"heapUsedBytes": 68852596,
"domNodes": -262,
"jsHeapTotalBytes": 5537792,
"scriptDurationMs": 373.159,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-zoom",
"durationMs": 3071.364000000017,
"styleRecalcs": 67,
"styleRecalcDurationMs": 17.535,
"layouts": 60,
"layoutDurationMs": 6.976999999999999,
"taskDurationMs": 1079.491,
"heapDeltaBytes": -7768124,
"heapUsedBytes": 54191160,
"domNodes": -261,
"jsHeapTotalBytes": 3497984,
"scriptDurationMs": 406.81800000000004,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2011.1709999999903,
"styleRecalcs": 9,
"styleRecalcDurationMs": 5.558999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 419.57500000000005,
"heapDeltaBytes": 16780324,
"heapUsedBytes": 75855212,
"domNodes": -251,
"jsHeapTotalBytes": -32768,
"scriptDurationMs": 62.46499999999999,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2025.3010000000131,
"styleRecalcs": 9,
"styleRecalcDurationMs": 5.861999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 408.586,
"heapDeltaBytes": 4965976,
"heapUsedBytes": 63781324,
"domNodes": -260,
"jsHeapTotalBytes": 557056,
"scriptDurationMs": 62.682999999999986,
"eventListeners": -125,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 1995.712000000026,
"styleRecalcs": 9,
"styleRecalcDurationMs": 5.935000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 430.51599999999996,
"heapDeltaBytes": 4019928,
"heapUsedBytes": 63051464,
"domNodes": -260,
"jsHeapTotalBytes": 229376,
"scriptDurationMs": 66.82000000000001,
"eventListeners": -125,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 455.0490000000309,
"styleRecalcs": 48,
"styleRecalcDurationMs": 9.850000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 282.10699999999997,
"heapDeltaBytes": 9903108,
"heapUsedBytes": 58986268,
"domNodes": 22,
"jsHeapTotalBytes": 15466496,
"scriptDurationMs": 98.449,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 448.3539999999948,
"styleRecalcs": 49,
"styleRecalcDurationMs": 10.033,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 291.37600000000003,
"heapDeltaBytes": -11799260,
"heapUsedBytes": 54495000,
"domNodes": 23,
"jsHeapTotalBytes": 19759104,
"scriptDurationMs": 97.535,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 470.49600000002556,
"styleRecalcs": 48,
"styleRecalcDurationMs": 10.195,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 290.53200000000004,
"heapDeltaBytes": 9551496,
"heapUsedBytes": 58834388,
"domNodes": 22,
"jsHeapTotalBytes": 15466496,
"scriptDurationMs": 102.91300000000001,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-idle",
"durationMs": 1990.9119999999803,
"styleRecalcs": 11,
"styleRecalcDurationMs": 6.9220000000000015,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 279.00199999999995,
"heapDeltaBytes": 1219904,
"heapUsedBytes": 67784528,
"domNodes": 22,
"jsHeapTotalBytes": 18710528,
"scriptDurationMs": 15.35,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2001.7149999999901,
"styleRecalcs": 11,
"styleRecalcDurationMs": 7.364000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 268.95799999999997,
"heapDeltaBytes": 22985832,
"heapUsedBytes": 70891136,
"domNodes": 22,
"jsHeapTotalBytes": 14680064,
"scriptDurationMs": 15.698999999999998,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2003.67899999992,
"styleRecalcs": 10,
"styleRecalcDurationMs": 6.222,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 278.444,
"heapDeltaBytes": 1982524,
"heapUsedBytes": 68274456,
"domNodes": 19,
"jsHeapTotalBytes": 20709376,
"scriptDurationMs": 11.88,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1673.64299999997,
"styleRecalcs": 76,
"styleRecalcDurationMs": 28.798,
"layouts": 16,
"layoutDurationMs": 3.645,
"taskDurationMs": 512.3870000000001,
"heapDeltaBytes": -7063000,
"heapUsedBytes": 59331636,
"domNodes": 62,
"jsHeapTotalBytes": 19234816,
"scriptDurationMs": 72.39500000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66666666666665,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1690.1890000000321,
"styleRecalcs": 76,
"styleRecalcDurationMs": 29.614,
"layouts": 16,
"layoutDurationMs": 3.557,
"taskDurationMs": 557.532,
"heapDeltaBytes": -23391360,
"heapUsedBytes": 43224348,
"domNodes": 62,
"jsHeapTotalBytes": 19361792,
"scriptDurationMs": 72.13,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1675.9319999999889,
"styleRecalcs": 76,
"styleRecalcDurationMs": 31.249,
"layouts": 16,
"layoutDurationMs": 3.993,
"taskDurationMs": 582.1129999999999,
"heapDeltaBytes": -14973100,
"heapUsedBytes": 51700656,
"domNodes": -256,
"jsHeapTotalBytes": 18575360,
"scriptDurationMs": 75.712,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "viewport-pan-sweep",
"durationMs": 8096.8210000000345,
"styleRecalcs": 250,
"styleRecalcDurationMs": 49.434,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2775.592,
"heapDeltaBytes": 16246440,
"heapUsedBytes": 72929964,
"domNodes": -256,
"jsHeapTotalBytes": 2334720,
"scriptDurationMs": 943.683,
"eventListeners": -109,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8100.706999999943,
"styleRecalcs": 249,
"styleRecalcDurationMs": 48.333,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2800.8450000000003,
"heapDeltaBytes": 10384532,
"heapUsedBytes": 68323600,
"domNodes": -261,
"jsHeapTotalBytes": 6201344,
"scriptDurationMs": 919.5400000000001,
"eventListeners": -111,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.80000000000109
},
{
"name": "viewport-pan-sweep",
"durationMs": 8114.850000000047,
"styleRecalcs": 250,
"styleRecalcDurationMs": 49.543000000000006,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2844.718,
"heapDeltaBytes": 20900160,
"heapUsedBytes": 78199268,
"domNodes": -252,
"jsHeapTotalBytes": 2007040,
"scriptDurationMs": 954.7410000000001,
"eventListeners": -109,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 10303.762000000006,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10258.838,
"heapDeltaBytes": -46803168,
"heapUsedBytes": 174605540,
"domNodes": -9850,
"jsHeapTotalBytes": 28372992,
"scriptDurationMs": 517.231,
"eventListeners": -23963,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 10664.013999999952,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10654.411,
"heapDeltaBytes": -54155012,
"heapUsedBytes": 174873284,
"domNodes": -9850,
"jsHeapTotalBytes": 27848704,
"scriptDurationMs": 497.802,
"eventListeners": -23963,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.219999999999953,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-idle",
"durationMs": 10337.23299999997,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10310.01,
"heapDeltaBytes": -46469668,
"heapUsedBytes": 174313180,
"domNodes": -9858,
"jsHeapTotalBytes": 25751552,
"scriptDurationMs": 473.14199999999994,
"eventListeners": -23963,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.80000000000109
},
{
"name": "vue-large-graph-pan",
"durationMs": 12130.983999999955,
"styleRecalcs": 64,
"styleRecalcDurationMs": 15.372999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12114.259,
"heapDeltaBytes": -34524900,
"heapUsedBytes": 180541404,
"domNodes": -9850,
"jsHeapTotalBytes": 25530368,
"scriptDurationMs": 690.348,
"eventListeners": -23961,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333338,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-pan",
"durationMs": 12535.652000000027,
"styleRecalcs": 65,
"styleRecalcDurationMs": 16.410000000000007,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12510.493999999997,
"heapDeltaBytes": -47580348,
"heapUsedBytes": 165996820,
"domNodes": -9854,
"jsHeapTotalBytes": -10424320,
"scriptDurationMs": 744.311,
"eventListeners": -23957,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 12606.820999999967,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.601000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12584.615,
"heapDeltaBytes": -49859136,
"heapUsedBytes": 166648280,
"domNodes": -9850,
"jsHeapTotalBytes": -10162176,
"scriptDurationMs": 737.084,
"eventListeners": -23955,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 442.3979999999119,
"styleRecalcs": 17,
"styleRecalcDurationMs": 20.984999999999996,
"layouts": 5,
"layoutDurationMs": 1.368,
"taskDurationMs": 99.76700000000001,
"heapDeltaBytes": 5272248,
"heapUsedBytes": 54718280,
"domNodes": 167,
"jsHeapTotalBytes": 262144,
"scriptDurationMs": 19.349999999999998,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "workflow-execution",
"durationMs": 459.41000000004806,
"styleRecalcs": 16,
"styleRecalcDurationMs": 19.312999999999995,
"layouts": 5,
"layoutDurationMs": 1.145,
"taskDurationMs": 112.119,
"heapDeltaBytes": -14823208,
"heapUsedBytes": 52797492,
"domNodes": 156,
"jsHeapTotalBytes": 4030464,
"scriptDurationMs": 23.482999999999997,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "workflow-execution",
"durationMs": 437.4670000000833,
"styleRecalcs": 20,
"styleRecalcDurationMs": 21.223,
"layouts": 6,
"layoutDurationMs": 1.391,
"taskDurationMs": 117.19399999999999,
"heapDeltaBytes": -14958420,
"heapUsedBytes": 52736276,
"domNodes": 159,
"jsHeapTotalBytes": 4030464,
"scriptDurationMs": 22.808000000000003,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
}
]
} |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/extensions/core/load3d/SceneManager.ts (1)
335-453:⚠️ Potential issue | 🟠 MajorEnsure renderer state is always restored on capture failure.
captureScene()mutates renderer size/pixel ratio/clear state, but restoration only happens on the success path. If any step throws, the renderer can stay in a corrupted state for later renders/captures.Suggested fix (move restoration to
finally)- captureScene( - width: number, - height: number - ): Promise<{ scene: string; mask: string; normal: string }> { - return new Promise(async (resolve, reject) => { - try { + async captureScene( + width: number, + height: number + ): Promise<{ scene: string; mask: string; normal: string }> { + const originalSize = new THREE.Vector2() + this.renderer.getSize(originalSize) + const originalPixelRatio = this.renderer.getPixelRatio() + const originalClearColor = this.renderer.getClearColor(new THREE.Color()) + const originalClearAlpha = this.renderer.getClearAlpha() + const originalOutputColorSpace = this.renderer.outputColorSpace + const originalMaterials = new Map< + THREE.Mesh, + THREE.Material | THREE.Material[] + >() + const gridVisible = this.gridHelper.visible + try { // ... existing capture logic ... - resolve({ + return { scene: sceneData, mask: maskData, normal: normalData - }) - } catch (error) { - reject(error) + } + } finally { + this.scene.traverse((child) => { + if (child instanceof THREE.Mesh) { + const originalMaterial = originalMaterials.get(child) + if (originalMaterial) child.material = originalMaterial + } + }) + this.gridHelper.visible = gridVisible + this.renderer.setClearColor(originalClearColor, originalClearAlpha) + this.renderer.setPixelRatio(originalPixelRatio) + this.renderer.setSize(originalSize.x, originalSize.y) + this.renderer.outputColorSpace = originalOutputColorSpace + this.handleResize(originalSize.x, originalSize.y) } - }) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/extensions/core/load3d/SceneManager.ts` around lines 335 - 453, captureScene currently mutates renderer state (pixelRatio, size, clear color, outputColorSpace), swaps mesh materials and gridHelper visibility but only restores these on the success path; move all restoration logic (reset renderer pixel ratio/size/outputColorSpace/clearColor/clearAlpha, restore originalMaterials for meshes, restore gridHelper.visible and call handleResize(originalSize.x, originalSize.y)) into a finally block so it always runs on success or error, keeping the try for rendering steps and catch to reject; ensure the finally runs before resolve/reject by either using async/await (avoid mixing new Promise with async) or calling the restore code inside a finally in the existing Promise wrapper; reference symbols: captureScene, this.renderer, this.getActiveCamera(), this.backgroundTexture, this.backgroundMesh, this.updateBackgroundSize(), originalMaterials map, this.scene.traverse(), this.gridHelper, and this.handleResize().
🧹 Nitpick comments (1)
src/extensions/core/load3d/Load3d.test.ts (1)
387-417: Assert call order (setPixelRatiobeforesetSize) to prevent regressions.These tests validate the pixel-ratio value, but not the ordering invariant that this fix depends on.
Suggested test hardening
- Object.assign(ctx.load3d, { - renderer: { domElement: canvas, setSize: vi.fn(), setPixelRatio }, + const setSize = vi.fn() + Object.assign(ctx.load3d, { + renderer: { domElement: canvas, setSize, setPixelRatio }, getZoomScaleCallback: () => 2.5, targetWidth: 0, targetHeight: 0, isViewerMode: false, cameraManager: { ...ctx.cameraManager, handleResize: vi.fn() }, sceneManager: { ...ctx.sceneManager, handleResize: vi.fn() } }) ctx.load3d.handleResize() expect(setPixelRatio).toHaveBeenCalledWith(2.5) + expect(setPixelRatio.mock.invocationCallOrder[0]).toBeLessThan( + setSize.mock.invocationCallOrder[0] + )Also applies to: 419-449
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/extensions/core/load3d/Load3d.test.ts` around lines 387 - 417, Update the test for ctx.load3d.handleResize to assert call order: ensure renderer.setPixelRatio is called with the value from getZoomScaleCallback before renderer.setSize is invoked; locate the test that assigns ctx.load3d with renderer: { domElement: canvas, setSize: vi.fn(), setPixelRatio } and replace/add an assertion that verifies setPixelRatio was called prior to setSize (i.e., check call order on setPixelRatio and setSize mocks) — apply the same change to the similar test covering lines ~419-449.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/extensions/core/load3d/SceneManager.ts`:
- Around line 335-453: captureScene currently mutates renderer state
(pixelRatio, size, clear color, outputColorSpace), swaps mesh materials and
gridHelper visibility but only restores these on the success path; move all
restoration logic (reset renderer pixel
ratio/size/outputColorSpace/clearColor/clearAlpha, restore originalMaterials for
meshes, restore gridHelper.visible and call handleResize(originalSize.x,
originalSize.y)) into a finally block so it always runs on success or error,
keeping the try for rendering steps and catch to reject; ensure the finally runs
before resolve/reject by either using async/await (avoid mixing new Promise with
async) or calling the restore code inside a finally in the existing Promise
wrapper; reference symbols: captureScene, this.renderer, this.getActiveCamera(),
this.backgroundTexture, this.backgroundMesh, this.updateBackgroundSize(),
originalMaterials map, this.scene.traverse(), this.gridHelper, and
this.handleResize().
---
Nitpick comments:
In `@src/extensions/core/load3d/Load3d.test.ts`:
- Around line 387-417: Update the test for ctx.load3d.handleResize to assert
call order: ensure renderer.setPixelRatio is called with the value from
getZoomScaleCallback before renderer.setSize is invoked; locate the test that
assigns ctx.load3d with renderer: { domElement: canvas, setSize: vi.fn(),
setPixelRatio } and replace/add an assertion that verifies setPixelRatio was
called prior to setSize (i.e., check call order on setPixelRatio and setSize
mocks) — apply the same change to the similar test covering lines ~419-449.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 154e7f87-65b8-4551-8947-8d39801d5b0e
📒 Files selected for processing (6)
src/composables/useLoad3d.test.tssrc/composables/useLoad3d.tssrc/extensions/core/load3d/Load3d.test.tssrc/extensions/core/load3d/Load3d.tssrc/extensions/core/load3d/SceneManager.tssrc/extensions/core/load3d/interfaces.ts
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## main #11734 +/- ##
==========================================
+ Coverage 51.90% 52.37% +0.47%
==========================================
Files 1376 1376
Lines 70445 70460 +15
Branches 18716 19631 +915
==========================================
+ Hits 36562 36902 +340
+ Misses 33336 33011 -325
Partials 547 547
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 23 files with indirect coverage changes 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
browser_tests/tests/load3d/load3dLod.spec.ts (1)
20-22: Remove redundant inline comments in the test bodyThese comments restate what the code already expresses and should be removed to match repo guidance.
♻️ Suggested cleanup
- // Zoom in — ds.scale increases, triggering appScalePercentage to update, - // which fires the watch in useLoad3d that calls handleResize with the - // new zoom-based pixel ratio. await comfyPage.canvasOps.zoom(-120, 5) - // Physical pixel count must grow: canvas.width = clientWidth × ds.scale await expect .poll(() => load3d.canvas.evaluate((el: HTMLCanvasElement) => el.width)) .toBeGreaterThan(initialWidth)As per coding guidelines: "Avoid new usage of code comments; do not add or retain redundant comments."
Also applies to: 25-26
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@browser_tests/tests/load3d/load3dLod.spec.ts` around lines 20 - 22, Remove the redundant inline comments in the test body that merely restate the code behavior (the blocks describing ds.scale increasing, appScalePercentage updating, and the useLoad3d watch calling handleResize), and also remove the similar comment pair later in the test; leave the code and assertions unchanged (references to ds.scale, appScalePercentage, useLoad3d, and handleResize help you locate the exact comment blocks to delete).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@browser_tests/tests/load3d/load3dLod.spec.ts`:
- Around line 20-22: Remove the redundant inline comments in the test body that
merely restate the code behavior (the blocks describing ds.scale increasing,
appScalePercentage updating, and the useLoad3d watch calling handleResize), and
also remove the similar comment pair later in the test; leave the code and
assertions unchanged (references to ds.scale, appScalePercentage, useLoad3d, and
handleResize help you locate the exact comment blocks to delete).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5148e3cd-cdaf-416a-9814-f7fa41ea2b37
📒 Files selected for processing (1)
browser_tests/tests/load3d/load3dLod.spec.ts
dante01yoon
left a comment
There was a problem hiding this comment.
Thanks for the focused Load3D fix. The unit-level pieces look reasonable, and I verified the focused unit slices locally: pnpm exec vitest run src/composables/useLoad3d.test.ts src/extensions/core/load3d/Load3d.test.ts passes with 99 tests.
issue: I need to request changes because the PR’s own browser regression test is failing on the current head. The failing check is playwright-tests-chromium-sharded (3, 8): after zooming in, the Load3D canvas backing width remains 374 instead of increasing, which means the user-visible zoom-resolution path is not yet proven to work. codecov/patch is also red.
The separate title-editor test in that shard is reported flaky, but the Load3D LOD test itself is the hard failure.
| await comfyPage.canvasOps.zoom(-120, 5) | ||
|
|
||
| // Physical pixel count must grow: canvas.width = clientWidth × ds.scale | ||
| await expect |
There was a problem hiding this comment.
issue: This assertion is currently failing in CI on this PR head. The log shows initialWidth is 374, and after canvasOps.zoom(-120, 5) the polled canvas.width is still 374 through all retries. Since this test is the regression coverage for the feature, we should fix the actual zoom-to-resize path (or the test setup if it is not exercising the real zoom path) before merging.
Summary
Preview 3D and Animation nodes were stuck at the LOD from initial page load because CSS
scale3dtransforms don't affectclientWidth/clientHeight—handleResize()always received layout-space dimensions regardless of zoom level. This fix passesds.scaleas the renderer pixel ratio so the 3D scene renders at the correct visual resolution when the graph is zoomed in or out.Changes
Load3d.handleResize(), callrenderer.setPixelRatio(ds.scale)beforesetSizeso pixel density scales with canvas zoom. AgetZoomScalecallback is threaded throughLoad3DOptions→Load3dconstructor →handleResize. InuseLoad3d, a watcher oncanvasStore.appScalePercentagetriggershandleResizewhenever the zoom level changes.SceneManager.captureScene()to save and restore the renderer's logical size and pixel ratio around capture, so exact-pixel output is unaffected by the current zoom state.Review Focus
handleResizenow callssetPixelRatiobeforesetSize. Three.js renders atlogicalWidth × pixelRatiophysical pixels while CSS displays it atlogicalWidthCSS pixels — this is the standard pattern for HiDPI but here used to match the visual zoom level.captureScenemust resetpixelRatioto 1 sosetSize(w, h)produces exactlyw×hpixel output. It saves and restores both logical size and pixel ratio viarenderer.getSize()/renderer.getPixelRatio().getActivePinia()to avoid errors in unit tests and non-Pinia contexts.Test
after


before
Note
Cursor Bugbot is generating a summary for commit aa36082. Configure here.
┆Issue is synchronized with this Notion page by Unito