Skip to content

test: repair vacuous save/load round-trip tests for widget labels - #15603

Merged
DrJKL merged 5 commits into
feature/ecs-migrationfrom
cb__roundtrip-vacuity-audit
Aug 22, 2026
Merged

test: repair vacuous save/load round-trip tests for widget labels#15603
DrJKL merged 5 commits into
feature/ecs-migrationfrom
cb__roundtrip-vacuity-audit

Conversation

@christian-byrne

@christian-byrne christian-byrne commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Repairs three save/load tests that were passing without ever reading the JSON they produced, and adds a helper so the next author does not rediscover it.

Why they were vacuous

widgetValueStore is keyed graphId:nodeId:name. LGraph.clear() drops the store for the graph's pre-configure id (LGraph.ts:578), then LGraph._configureBase() re-adopts the payload's graph id (LGraph.ts:2566-2568). So new LGraph().configure(source.serialize()) ends up sharing the source graph's id — and the source's widget entries are still live under exactly the key the reloaded node looks up. registerWidget returns the existing state and the reloaded widget silently inherits the source's label.

Verified directly in the #13861 test: both graphs report id 8016438e-…, both widgets report widgetId 8016438e-…:1:text.

Numbers

Mutation A — reintroduce #13861's own root cause (delete if (input) input.label = label from renameWidget, src/utils/widgetUtil.ts:65), against LGraphNode.widgetLabelRename.regression.test.ts:

before after
tests killed 2 / 5 5 / 5

Survivors before: label survives a full graph serialize -> configure round-trip, label survives delete -> undo, clearing a rename reverts the label to its default after round-trip.

Mutation B — LGraphNode.serialize() drops label and localized_name from every input:

before after
killed in the #13861 file 1 / 5 3 / 5
killed across all 29 round-trip test files in src/ 1 / 922 4 / 923

Mutation C — total payload blinding of SubgraphNode.configure (argument replaced with { id, type }): should preserve renamed label through serialize/configure round-trip survived. It also survived Mutation B. It is killed only by a mutation that makes configure append instead of resetting inputs — so it is an input-list-reset test, not a round trip. Renamed accordingly, and a real definition-reload test added beside it (killed by Mutation B).

Unmutated control: 923 / 923 pass across the 29 files. oxlint --type-aware clean on the three changed files, with a non-vacuous control (an injected debugger is reported). oxfmt applied.

Changes

  • src/utils/__tests__/litegraphTestUtils.ts — new reloadSerializedGraph(serialized, graphFactory). Forces the payload through JSON.parse(JSON.stringify(...)) so live-object aliasing cannot leak, and drops the widget and preview stores for the payload's graph id before configure().
  • src/lib/litegraph/src/LGraphNode.widgetLabelRename.regression.test.ts — the three reload tests go through the helper. clearing a rename reverts the label gains a live-rename control arm, so a build where renaming never persisted at all cannot pass it by asserting undefined.
  • src/lib/litegraph/src/subgraph/SubgraphNode.test.ts — vacuous test renamed to keeps the renamed label after an in-place reconfigure with a comment explaining what it cannot see; new preserves a renamed label across a real definition reload added.

Scope

Test-only. No production change. The open question of whether LGraph.configure should also clear the store for the incoming graph id is deliberately not answered here — a store-amnesia probe at _configureBase killed 0 of 922 tests, so production rehydration is correct today, but it deserves an explicit decision rather than staying implicit.

Full audit and per-test verdict table: #15604

Widget state is keyed `graphId:nodeId:name` and `LGraph._configureBase`
re-adopts the payload's graph id, while `LGraph.clear()` only clears the
store under the graph's *pre-configure* id. A test that serializes and
reconfigures therefore leaves the source graph's `widgetValueStore`
entries live under exactly the key the reloaded node looks up, so the
reloaded node re-adopts them and the assertion passes without the
serialized payload ever being read.

Mutation-verified. Reintroducing #13861's own root cause (dropping
`input.label = label` from `renameWidget`) killed 2 of the 5 tests in
its regression file; after this change it kills 5 of 5. Making
`serialize()` drop every input label and localized_name killed 1 of 5
before, 3 of 5 after -- and across the 29 round-trip test files in the
repo it killed 1 of 922 tests before this change.

- add `reloadSerializedGraph` test helper: forces the payload through
  JSON and drops the widget/preview stores for the payload's graph id
- use it in the three reload tests in the #13861 regression file
- give 'clearing a rename reverts the label' a live-rename control arm,
  so a build where renaming never persisted cannot pass it
- rename SubgraphNode's 'preserve renamed label through
  serialize/configure round-trip' to what it actually asserts: it
  survives total payload blinding, so it is an in-place reconfigure test
- add a real definition-reload test alongside it, killed by the
  input-label serialization mutation
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 236a3f66-d279-4d01-96e3-34b6e541f942

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 08/22/2026, 04:20:38 AM UTC

Links

🎭 Playwright: ✅ 1991 passed, 0 failed

📊 Browser Reports
  • chromium: View Report (✅ 1970 / ❌ 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)

📦 Bundle Size

⏳ Size data collection in progress…

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 66.8 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 57.5 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 70.1 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 59.8 MB heap
large-graph-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 67.3 MB heap
large-graph-pan: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 80.6 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.7 MB heap
legacy-node-drag: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.6 MB heap
minimap-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 64.7 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 83.4 MB heap
subgraph-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 71.4 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 66.1 MB heap
subgraph-transition-enter: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 76ms TBT · 79.8 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 82.5 MB heap
vue-large-graph-idle: · 58.1 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 171.4 MB heap
vue-large-graph-pan: · 57.1 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 38ms TBT · 162.2 MB heap
workflow-execution: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 67.1 MB heap

ℹ️ No baseline found — significance unavailable.

Absolute values
Metric Value
canvas-idle: avg frame time 17ms
canvas-idle: p95 frame time 17ms
canvas-idle: layout duration 0ms
canvas-idle: style recalc duration 7ms
canvas-idle: layout count 0
canvas-idle: style recalc count 10
canvas-idle: task duration 417ms
canvas-idle: script duration 10ms
canvas-idle: TBT 0ms
canvas-idle: heap used 66.8 MB
canvas-idle: DOM nodes -280
canvas-idle: event listeners -185
canvas-mouse-sweep: avg frame time 17ms
canvas-mouse-sweep: p95 frame time 17ms
canvas-mouse-sweep: layout duration 4ms
canvas-mouse-sweep: style recalc duration 37ms
canvas-mouse-sweep: layout count 12
canvas-mouse-sweep: style recalc count 76
canvas-mouse-sweep: task duration 938ms
canvas-mouse-sweep: script duration 125ms
canvas-mouse-sweep: TBT 0ms
canvas-mouse-sweep: heap used 57.5 MB
canvas-mouse-sweep: DOM nodes -281
canvas-mouse-sweep: event listeners -183
canvas-zoom-sweep: avg frame time 17ms
canvas-zoom-sweep: p95 frame time 17ms
canvas-zoom-sweep: layout duration 1ms
canvas-zoom-sweep: style recalc duration 15ms
canvas-zoom-sweep: layout count 6
canvas-zoom-sweep: style recalc count 31
canvas-zoom-sweep: task duration 290ms
canvas-zoom-sweep: script duration 12ms
canvas-zoom-sweep: TBT 0ms
canvas-zoom-sweep: heap used 70.1 MB
canvas-zoom-sweep: DOM nodes 76
canvas-zoom-sweep: event listeners 20
dom-widget-clipping: avg frame time 17ms
dom-widget-clipping: p95 frame time 17ms
dom-widget-clipping: layout duration 0ms
dom-widget-clipping: style recalc duration 6ms
dom-widget-clipping: layout count 0
dom-widget-clipping: style recalc count 10
dom-widget-clipping: task duration 330ms
dom-widget-clipping: script duration 29ms
dom-widget-clipping: TBT 0ms
dom-widget-clipping: heap used 59.8 MB
dom-widget-clipping: DOM nodes 15
dom-widget-clipping: event listeners 1
large-graph-idle: avg frame time 17ms
large-graph-idle: p95 frame time 17ms
large-graph-idle: layout duration 0ms
large-graph-idle: style recalc duration 5ms
large-graph-idle: layout count 0
large-graph-idle: style recalc count 6
large-graph-idle: task duration 455ms
large-graph-idle: script duration 36ms
large-graph-idle: TBT 0ms
large-graph-idle: heap used 67.3 MB
large-graph-idle: DOM nodes -271
large-graph-idle: event listeners -167
large-graph-pan: avg frame time 17ms
large-graph-pan: p95 frame time 17ms
large-graph-pan: layout duration 0ms
large-graph-pan: style recalc duration 14ms
large-graph-pan: layout count 0
large-graph-pan: style recalc count 68
large-graph-pan: task duration 2013ms
large-graph-pan: script duration 674ms
large-graph-pan: TBT 0ms
large-graph-pan: heap used 80.6 MB
large-graph-pan: DOM nodes -265
large-graph-pan: event listeners -164
large-graph-zoom: avg frame time 17ms
large-graph-zoom: p95 frame time 17ms
large-graph-zoom: layout duration 10ms
large-graph-zoom: style recalc duration 16ms
large-graph-zoom: layout count 60
large-graph-zoom: style recalc count 69
large-graph-zoom: task duration 2042ms
large-graph-zoom: script duration 659ms
large-graph-zoom: TBT 0ms
large-graph-zoom: heap used 69.7 MB
large-graph-zoom: DOM nodes -256
large-graph-zoom: event listeners -176
legacy-node-drag: avg frame time 17ms
legacy-node-drag: p95 frame time 17ms
legacy-node-drag: layout duration 0ms
legacy-node-drag: style recalc duration 7ms
legacy-node-drag: layout count 0
legacy-node-drag: style recalc count 43
legacy-node-drag: task duration 1755ms
legacy-node-drag: script duration 195ms
legacy-node-drag: TBT 0ms
legacy-node-drag: heap used 69.6 MB
legacy-node-drag: DOM nodes -141
legacy-node-drag: event listeners 94
minimap-idle: avg frame time 17ms
minimap-idle: p95 frame time 17ms
minimap-idle: layout duration 0ms
minimap-idle: style recalc duration 5ms
minimap-idle: layout count 0
minimap-idle: style recalc count 7
minimap-idle: task duration 487ms
minimap-idle: script duration 37ms
minimap-idle: TBT 0ms
minimap-idle: heap used 64.7 MB
minimap-idle: DOM nodes -278
minimap-idle: event listeners -184
subgraph-dom-widget-clipping: avg frame time 17ms
subgraph-dom-widget-clipping: p95 frame time 17ms
subgraph-dom-widget-clipping: layout duration 0ms
subgraph-dom-widget-clipping: style recalc duration 11ms
subgraph-dom-widget-clipping: layout count 0
subgraph-dom-widget-clipping: style recalc count 47
subgraph-dom-widget-clipping: task duration 379ms
subgraph-dom-widget-clipping: script duration 100ms
subgraph-dom-widget-clipping: TBT 0ms
subgraph-dom-widget-clipping: heap used 83.4 MB
subgraph-dom-widget-clipping: DOM nodes 19
subgraph-dom-widget-clipping: event listeners 8
subgraph-idle: avg frame time 17ms
subgraph-idle: p95 frame time 17ms
subgraph-idle: layout duration 0ms
subgraph-idle: style recalc duration 9ms
subgraph-idle: layout count 0
subgraph-idle: style recalc count 10
subgraph-idle: task duration 394ms
subgraph-idle: script duration 9ms
subgraph-idle: TBT 0ms
subgraph-idle: heap used 71.4 MB
subgraph-idle: DOM nodes -294
subgraph-idle: event listeners -214
subgraph-mouse-sweep: avg frame time 17ms
subgraph-mouse-sweep: p95 frame time 17ms
subgraph-mouse-sweep: layout duration 4ms
subgraph-mouse-sweep: style recalc duration 34ms
subgraph-mouse-sweep: layout count 16
subgraph-mouse-sweep: style recalc count 75
subgraph-mouse-sweep: task duration 829ms
subgraph-mouse-sweep: script duration 96ms
subgraph-mouse-sweep: TBT 0ms
subgraph-mouse-sweep: heap used 66.1 MB
subgraph-mouse-sweep: DOM nodes -298
subgraph-mouse-sweep: event listeners -214
subgraph-transition-enter: avg frame time 17ms
subgraph-transition-enter: p95 frame time 17ms
subgraph-transition-enter: layout duration 10ms
subgraph-transition-enter: style recalc duration 24ms
subgraph-transition-enter: layout count 14
subgraph-transition-enter: style recalc count 19
subgraph-transition-enter: task duration 717ms
subgraph-transition-enter: script duration 23ms
subgraph-transition-enter: TBT 76ms
subgraph-transition-enter: heap used 79.8 MB
subgraph-transition-enter: DOM nodes 13671
subgraph-transition-enter: event listeners 2383
viewport-pan-sweep: avg frame time 17ms
viewport-pan-sweep: p95 frame time 17ms
viewport-pan-sweep: layout duration 0ms
viewport-pan-sweep: style recalc duration 45ms
viewport-pan-sweep: layout count 0
viewport-pan-sweep: style recalc count 248
viewport-pan-sweep: task duration 7077ms
viewport-pan-sweep: script duration 2335ms
viewport-pan-sweep: TBT 0ms
viewport-pan-sweep: heap used 82.5 MB
viewport-pan-sweep: DOM nodes -274
viewport-pan-sweep: event listeners -142
vue-large-graph-idle: avg frame time 17ms
vue-large-graph-idle: p95 frame time 17ms
vue-large-graph-idle: layout duration 0ms
vue-large-graph-idle: style recalc duration 0ms
vue-large-graph-idle: layout count 0
vue-large-graph-idle: style recalc count 0
vue-large-graph-idle: task duration 12451ms
vue-large-graph-idle: script duration 231ms
vue-large-graph-idle: TBT 0ms
vue-large-graph-idle: heap used 171.4 MB
vue-large-graph-idle: DOM nodes -6548
vue-large-graph-idle: event listeners -7474
vue-large-graph-pan: avg frame time 18ms
vue-large-graph-pan: p95 frame time 17ms
vue-large-graph-pan: layout duration 0ms
vue-large-graph-pan: style recalc duration 18ms
vue-large-graph-pan: layout count 0
vue-large-graph-pan: style recalc count 87
vue-large-graph-pan: task duration 15407ms
vue-large-graph-pan: script duration 759ms
vue-large-graph-pan: TBT 38ms
vue-large-graph-pan: heap used 162.2 MB
vue-large-graph-pan: DOM nodes -6550
vue-large-graph-pan: event listeners -7473
workflow-execution: avg frame time 17ms
workflow-execution: p95 frame time 17ms
workflow-execution: layout duration 1ms
workflow-execution: style recalc duration 17ms
workflow-execution: layout count 3
workflow-execution: style recalc count 12
workflow-execution: task duration 86ms
workflow-execution: script duration 5ms
workflow-execution: TBT 0ms
workflow-execution: heap used 67.1 MB
workflow-execution: DOM nodes 126
workflow-execution: event listeners 62
Raw data
{
  "timestamp": "2026-08-22T03:03:29.198Z",
  "gitSha": "c79f7e2f64fe355ff00dff25e59401f60c765031",
  "branch": "cb__roundtrip-vacuity-audit",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2004.480000000001,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 7.189999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 360.55199999999996,
      "heapDeltaBytes": -128320,
      "heapUsedBytes": 68939916,
      "domNodes": -278,
      "jsHeapTotalBytes": 4186112,
      "scriptDurationMs": 8.262,
      "eventListeners": -187,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-idle",
      "durationMs": 2024.1889999999785,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 7.336,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 472.818,
      "heapDeltaBytes": 1378812,
      "heapUsedBytes": 71069028,
      "domNodes": -281,
      "jsHeapTotalBytes": 4710400,
      "scriptDurationMs": 11.513,
      "eventListeners": -183,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1839.8590000000468,
      "styleRecalcs": 76,
      "styleRecalcDurationMs": 34.634,
      "layouts": 12,
      "layoutDurationMs": 3.477,
      "taskDurationMs": 887.3549999999999,
      "heapDeltaBytes": -9297392,
      "heapUsedBytes": 60001544,
      "domNodes": -280,
      "jsHeapTotalBytes": 5234688,
      "scriptDurationMs": 117.786,
      "eventListeners": -183,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1967.759000000001,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 39.311,
      "layouts": 12,
      "layoutDurationMs": 3.656,
      "taskDurationMs": 989.569,
      "heapDeltaBytes": -1246208,
      "heapUsedBytes": 60649784,
      "domNodes": -282,
      "jsHeapTotalBytes": 5496832,
      "scriptDurationMs": 131.85999999999999,
      "eventListeners": -183,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1664.447999999993,
      "styleRecalcs": 31,
      "styleRecalcDurationMs": 13.969000000000003,
      "layouts": 6,
      "layoutDurationMs": 0.6100000000000001,
      "taskDurationMs": 258.93499999999995,
      "heapDeltaBytes": 4054528,
      "heapUsedBytes": 73386760,
      "domNodes": 77,
      "jsHeapTotalBytes": 4980736,
      "scriptDurationMs": 10.393,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1708.737000000042,
      "styleRecalcs": 30,
      "styleRecalcDurationMs": 15.780000000000003,
      "layouts": 6,
      "layoutDurationMs": 0.664,
      "taskDurationMs": 321.788,
      "heapDeltaBytes": 4060948,
      "heapUsedBytes": 73570780,
      "domNodes": 75,
      "jsHeapTotalBytes": 5242880,
      "scriptDurationMs": 14.466999999999999,
      "eventListeners": 21,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 624.7490000000084,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 5.119000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 321.571,
      "heapDeltaBytes": -7431888,
      "heapUsedBytes": 61524876,
      "domNodes": 14,
      "jsHeapTotalBytes": 7340032,
      "scriptDurationMs": 27.365,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 691.353999999933,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 6.410000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 339.28400000000005,
      "heapDeltaBytes": -6037164,
      "heapUsedBytes": 63844592,
      "domNodes": 16,
      "jsHeapTotalBytes": 5505024,
      "scriptDurationMs": 29.900999999999996,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2025.291999999979,
      "styleRecalcs": 6,
      "styleRecalcDurationMs": 4.546999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 385.193,
      "heapDeltaBytes": -17538164,
      "heapUsedBytes": 72611484,
      "domNodes": -266,
      "jsHeapTotalBytes": -2625536,
      "scriptDurationMs": 32.78300000000001,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2041.2730000000465,
      "styleRecalcs": 6,
      "styleRecalcDurationMs": 5.469999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 523.9979999999999,
      "heapDeltaBytes": -22080420,
      "heapUsedBytes": 68629516,
      "domNodes": -276,
      "jsHeapTotalBytes": 2355200,
      "scriptDurationMs": 39.95900000000001,
      "eventListeners": -181,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-pan",
      "durationMs": 3124.6459999999843,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 14.321000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1884.82,
      "heapDeltaBytes": 7617716,
      "heapUsedBytes": 91709392,
      "domNodes": -282,
      "jsHeapTotalBytes": 5165056,
      "scriptDurationMs": 636.6719999999999,
      "eventListeners": -179,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 3269.4770000000517,
      "styleRecalcs": 66,
      "styleRecalcDurationMs": 13.676999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 2141.736,
      "heapDeltaBytes": -13551556,
      "heapUsedBytes": 77308656,
      "domNodes": -248,
      "jsHeapTotalBytes": 2056192,
      "scriptDurationMs": 710.599,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 4187.117,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 15.438999999999998,
      "layouts": 60,
      "layoutDurationMs": 8.936000000000002,
      "taskDurationMs": 1898.4489999999998,
      "heapDeltaBytes": 586752,
      "heapUsedBytes": 73098024,
      "domNodes": -256,
      "jsHeapTotalBytes": -1875968,
      "scriptDurationMs": 635.437,
      "eventListeners": -175,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 4425.604000000021,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 16.596000000000004,
      "layouts": 60,
      "layoutDurationMs": 10.142999999999999,
      "taskDurationMs": 2185.248,
      "heapDeltaBytes": -15420572,
      "heapUsedBytes": 73110044,
      "domNodes": -256,
      "jsHeapTotalBytes": 1269760,
      "scriptDurationMs": 683.402,
      "eventListeners": -177,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "legacy-node-drag",
      "durationMs": 2654.943000000003,
      "styleRecalcs": 43,
      "styleRecalcDurationMs": 7.445,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1638.836,
      "heapDeltaBytes": 12596596,
      "heapUsedBytes": 78600964,
      "domNodes": 4,
      "jsHeapTotalBytes": 417792,
      "scriptDurationMs": 174.594,
      "eventListeners": 186,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "legacy-node-drag",
      "durationMs": 3113.061000000016,
      "styleRecalcs": 43,
      "styleRecalcDurationMs": 7.431,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1871.487,
      "heapDeltaBytes": -4178252,
      "heapUsedBytes": 67283688,
      "domNodes": -285,
      "jsHeapTotalBytes": -1089536,
      "scriptDurationMs": 216.29800000000003,
      "eventListeners": 1,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 2020.887000000016,
      "styleRecalcs": 7,
      "styleRecalcDurationMs": 5.646000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 440.232,
      "heapDeltaBytes": -21959788,
      "heapUsedBytes": 70032216,
      "domNodes": -273,
      "jsHeapTotalBytes": 3665920,
      "scriptDurationMs": 33.119,
      "eventListeners": -183,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2037.0679999999766,
      "styleRecalcs": 6,
      "styleRecalcDurationMs": 5.109000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 534.702,
      "heapDeltaBytes": -11143964,
      "heapUsedBytes": 65627456,
      "domNodes": -282,
      "jsHeapTotalBytes": 2719744,
      "scriptDurationMs": 41.821000000000005,
      "eventListeners": -185,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 525.0819999999976,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 10.6,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 367.544,
      "heapDeltaBytes": 17637784,
      "heapUsedBytes": 87354388,
      "domNodes": 20,
      "jsHeapTotalBytes": 5242880,
      "scriptDurationMs": 97.158,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.799999999999727
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 574.3579999999611,
      "styleRecalcs": 46,
      "styleRecalcDurationMs": 10.557999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 390.242,
      "heapDeltaBytes": 17902208,
      "heapUsedBytes": 87541280,
      "domNodes": 18,
      "jsHeapTotalBytes": 5242880,
      "scriptDurationMs": 102.839,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2020.2600000000075,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 8.772,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 336.01300000000003,
      "heapDeltaBytes": 2090904,
      "heapUsedBytes": 71636096,
      "domNodes": -294,
      "jsHeapTotalBytes": 5234688,
      "scriptDurationMs": 7.459,
      "eventListeners": -229,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2007.4860000000854,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 8.633000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 451.703,
      "heapDeltaBytes": 8430692,
      "heapUsedBytes": 78111872,
      "domNodes": -294,
      "jsHeapTotalBytes": 4972544,
      "scriptDurationMs": 10.581999999999999,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1725.6949999999733,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 32.45000000000001,
      "layouts": 16,
      "layoutDurationMs": 4.352,
      "taskDurationMs": 780.1139999999999,
      "heapDeltaBytes": 8706644,
      "heapUsedBytes": 78604772,
      "domNodes": -299,
      "jsHeapTotalBytes": 6021120,
      "scriptDurationMs": 91.96499999999999,
      "eventListeners": -229,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1724.9500000000353,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 36.201,
      "layouts": 16,
      "layoutDurationMs": 4.64,
      "taskDurationMs": 878.529,
      "heapDeltaBytes": -9742788,
      "heapUsedBytes": 59999964,
      "domNodes": -296,
      "jsHeapTotalBytes": 6283264,
      "scriptDurationMs": 100.25300000000001,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-transition-enter",
      "durationMs": 916.4000000000101,
      "styleRecalcs": 19,
      "styleRecalcDurationMs": 23.771999999999995,
      "layouts": 14,
      "layoutDurationMs": 10.106,
      "taskDurationMs": 716.7989999999998,
      "heapDeltaBytes": -7463948,
      "heapUsedBytes": 83638028,
      "domNodes": 13671,
      "jsHeapTotalBytes": 10485760,
      "scriptDurationMs": 22.77,
      "eventListeners": 2383,
      "totalBlockingTimeMs": 76,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 12004.457000000002,
      "styleRecalcs": 249,
      "styleRecalcDurationMs": 43.486999999999995,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 6559.871,
      "heapDeltaBytes": -12221956,
      "heapUsedBytes": 78751724,
      "domNodes": -264,
      "jsHeapTotalBytes": 3596288,
      "scriptDurationMs": 2174.359,
      "eventListeners": -127,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 12258.649999999989,
      "styleRecalcs": 247,
      "styleRecalcDurationMs": 45.582,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 7594.443000000001,
      "heapDeltaBytes": 3477528,
      "heapUsedBytes": 94170836,
      "domNodes": -284,
      "jsHeapTotalBytes": 4907008,
      "scriptDurationMs": 2495.638,
      "eventListeners": -157,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12754.401999999971,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12233.395999999999,
      "heapDeltaBytes": -10691208,
      "heapUsedBytes": 182322660,
      "domNodes": -6548,
      "jsHeapTotalBytes": 2871296,
      "scriptDurationMs": 225.439,
      "eventListeners": -7475,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.219999999999953,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 13221.525999999925,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12667.731000000002,
      "heapDeltaBytes": -25815248,
      "heapUsedBytes": 177225956,
      "domNodes": -6548,
      "jsHeapTotalBytes": -2580480,
      "scriptDurationMs": 236.74399999999997,
      "eventListeners": -7473,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.219999999999953,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 15649.681999999984,
      "styleRecalcs": 83,
      "styleRecalcDurationMs": 17.276000000000014,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 15210.329,
      "heapDeltaBytes": -32416504,
      "heapUsedBytes": 175673060,
      "domNodes": -6552,
      "jsHeapTotalBytes": -6860800,
      "scriptDurationMs": 759.5320000000002,
      "eventListeners": -7473,
      "totalBlockingTimeMs": 58,
      "frameDurationMs": 17.776666666666642,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 16053.022000000055,
      "styleRecalcs": 91,
      "styleRecalcDurationMs": 18.566,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 15604.098,
      "heapDeltaBytes": -42667784,
      "heapUsedBytes": 164529624,
      "domNodes": -6548,
      "jsHeapTotalBytes": -3653632,
      "scriptDurationMs": 758.193,
      "eventListeners": -7473,
      "totalBlockingTimeMs": 18,
      "frameDurationMs": 17.223333333333358,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "workflow-execution",
      "durationMs": 436.6459999999961,
      "styleRecalcs": 16,
      "styleRecalcDurationMs": 20.609999999999996,
      "layouts": 3,
      "layoutDurationMs": 0.7440000000000001,
      "taskDurationMs": 101.365,
      "heapDeltaBytes": 5460604,
      "heapUsedBytes": 71114788,
      "domNodes": 126,
      "jsHeapTotalBytes": 262144,
      "scriptDurationMs": 7.1579999999999995,
      "eventListeners": 99,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "workflow-execution",
      "durationMs": 101.86600000008639,
      "styleRecalcs": 7,
      "styleRecalcDurationMs": 13.598999999999998,
      "layouts": 2,
      "layoutDurationMs": 1.042,
      "taskDurationMs": 71.144,
      "heapDeltaBytes": 3275640,
      "heapUsedBytes": 69642488,
      "domNodes": 126,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 3.546999999999999,
      "eventListeners": 25,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.800000000000182
    }
  ]
}

@github-actions github-actions Bot added the risk:R3 PR risk grade (advisory shadow check; grader-owned) label Aug 22, 2026
@codecov

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

@@                   Coverage Diff                   @@
##           feature/ecs-migration   #15603    +/-   ##
=======================================================
  Coverage                  82.29%   82.30%            
=======================================================
  Files                       1900     1900            
  Lines                     107653   107660     +7     
  Branches                   33652    34065   +413     
=======================================================
+ Hits                       88595    88608    +13     
- Misses                     18720    18723     +3     
+ Partials                     338      329     -9     
Flag Coverage Δ
e2e 67.98% <ø> (ø)
unit 73.90% <100.00%> (+<0.01%) ⬆️

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

Files with missing lines Coverage Δ
src/utils/__tests__/litegraphTestUtils.ts 95.60% <100.00%> (+0.36%) ⬆️

... and 17 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.

Comment thread src/lib/litegraph/src/subgraph/SubgraphNode.test.ts Outdated
Comment on lines +467 to +472
LiteGraph.registerNodeType('test/interior-label-reload', InteriorNode)
onTestFinished(() => {
delete LiteGraph.registered_node_types['test/interior-label-reload']
})

const interiorNode = LiteGraph.createNode('test/interior-label-reload')!

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.

How do you feel about a const for this to make sure they're all consistently aligned?

Comment thread src/lib/litegraph/src/LGraphNode.widgetLabelRename.regression.test.ts Outdated
Comment thread src/lib/litegraph/src/subgraph/SubgraphNode.test.ts Outdated
Comment thread src/lib/litegraph/src/subgraph/SubgraphNode.test.ts Outdated
Comment on lines +402 to +421
/**
* Reloads a serialized graph the way a fresh page would, and returns the new
* graph.
*
* Save/load assertions written as `configure(...serialize())` are vacuous by
* default. `LGraph.clear()` drops the widget store under the graph's
* *pre-configure* id, but `LGraph._configureBase` then re-adopts the *payload's*
* graph id — and `widgetValueStore` is keyed `graphId:nodeId:name`. The source
* graph's entries are therefore still live under exactly the key the reloaded
* node looks up, so `registerWidget` returns the existing state and the
* assertion passes without the serialized payload ever being read.
*
* This helper removes both escape hatches: the payload is forced through
* `JSON.parse(JSON.stringify(...))` (so live-object aliasing cannot leak) and
* the widget/preview stores are dropped for the payload's graph id before
* `configure()` runs.
*
* Verify with a mutation: a save/load test that still passes when the producer
* stops writing the field under test never read the payload.
*/

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.

THE COMMENTS...

serialized: T,
graphFactory: () => LGraph
): LGraph {
const payload = JSON.parse(JSON.stringify(serialized)) as T

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.

So many casts

@DrJKL DrJKL 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.

Tests now exercise the serialized payload rather than reusing live widget state. The regression suite is mutation-verified, and the reload helper preserves the graph serialization types without unsafe casts.

Comment thread src/utils/__tests__/litegraphTestUtils.ts Outdated
@DrJKL
DrJKL merged commit 5002fae into feature/ecs-migration Aug 22, 2026
25 checks passed
@DrJKL
DrJKL deleted the cb__roundtrip-vacuity-audit branch August 22, 2026 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:R3 PR risk grade (advisory shadow check; grader-owned) size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants