Skip to content

fix: decouple subgraph preview/output display from the promotion system - #14569

Open
claude[bot] wants to merge 15 commits into
mainfrom
fix/decouple-subgraph-preview-outputs
Open

fix: decouple subgraph preview/output display from the promotion system#14569
claude[bot] wants to merge 15 commits into
mainfrom
fix/decouple-subgraph-preview-outputs

Conversation

@claude

@claude claude Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Requested by Christian Byrne · Slack thread

Summary

Fixes incident-94 bug #3: when a subgraph host has multiple interior nodes producing live previews concurrently (e.g. two KSamplers in one subgraph), only one of them showed on the host, and it could go stale.

Before: the only path from an interior node's live output to the SubgraphNode host was previewExposureStore. The root cause is not the exposure record itself but how exposures get created: a one-shot autoExposeKnownPreviewNodes pass that runs once, the first time the subgraph's previewExposures property is undefined. If a second interior node (a second KSampler, or one added after that one-shot pass already ran) never got an exposure, its live preview had nowhere to render on the host — invisible, even while executing.

After: the host also derives an ambient rollup, independent of promotion, by reading each direct interior node's own output/preview state straight from nodeOutputStore — the same store every regular node's own preview already reads from. Any interior node currently producing output shows on the host, whether or not anyone promoted it. An explicit exposure for a node still wins (keeps its promoted name/ordering); the ambient rollup only fills in nodes that have live output but no exposure. Two concurrently-executing KSamplers in one subgraph now both show and update independently instead of one overwriting/hiding the other.

How

  • src/composables/node/useAmbientSubgraphPreviews.ts (new): a computed that mirrors the shape of executionStore's nodeLocationProgressStates — which already bubbles up execution progress to a subgraph host independent of promotion — but for preview images. For a SubgraphNode host it walks host.subgraph.nodes (direct interior nodes only; nested SubgraphNodes derive their own previews independently) and, for each one with live output/preview data in nodeOutputStore, builds a preview entry via the same getNodeImageUrls a regular node's own preview already uses. Deliberately a computed, not a watch(), per this repo's existing review pushback on watcher-based state derivation.
  • src/composables/node/usePromotedPreviews.ts: exported the existing PromotedPreview type and getPreviewMediaType helper (previously private) so the new ambient composable can share them — no behavior change to the exposure-driven path itself.
  • src/renderer/extensions/vueNodes/components/LGraphNode.vue: added useAmbientSubgraphPreviews alongside the existing usePromotedPreviews, merged into a subgraphPreviews computed (exposure-driven entries first, then any ambient entries for interior nodes not already exposed, deduped by source node id), and swapped the render v-for / hasCustomContent check from promotedPreviews to subgraphPreviews. The render list already supported an arbitrary number of previews — no new render component needed.
  • previewExposureStore's schema, the promotion/exposure mechanism, and the canvasImagePreviewTypes.ts allowlist are untouched — this is purely additive.

Test plan

  • src/composables/node/useAmbientSubgraphPreviews.test.ts (new, red→green): unit tests the new rollup directly — empty/detached/no-output cases, a single exposed-free interior node with live output, and the core regression case of two concurrently-executing interior nodes producing distinct previews without either being registered as an exposure.
  • browser_tests/tests/subgraph/subgraphConcurrentPreviews.spec.ts + browser_tests/assets/subgraphs/subgraph-with-two-live-samplers.json (new): e2e regression with two KSamplers in one subgraph where only one has a baked-in previewExposures entry (simulating a node added after the subgraph's one-shot auto-expose already ran). Drives two live b_preview WS frames at distinct interior execution IDs and asserts both show on the host, and that each updates independently as new frames arrive.
  • Confirmed red on main: before the fix, the unit test fails because useAmbientSubgraphPreviews doesn't exist yet (module-not-found), and the e2e scenario only shows the exposed sampler's preview.
  • Confirmed green after the fix: both new test files pass; broader regression run (usePromotedPreviews, previewExposureStore, nodeOutputStore, executionStore, nodeIdentification, LGraphNode/LGraphNode.subgraph, promotionUtils — 31 files / 550 tests) all pass, so single-KSampler-in-subgraph and the SamplerCustomAdvanced allowlist-gap fix (fix: derive subgraph preview promotion for any interior node type #14551) are unaffected.
  • pnpm typecheck, pnpm typecheck:browser, and oxlint/eslint/oxfmt on all touched files are clean.
  • Not run: the full Playwright browser suite, since this sandbox has no live ComfyUI backend available (cross-org repo, can't attach comfyanonymous/ComfyUI alongside this session's Comfy-Org sources) — the e2e spec was verified via playwright test --list (compiles, discovers under @vue-nodes/@subgraph) and vue-tsc --project browser_tests/tsconfig.json, but not executed end-to-end. Recommend running it in CI/a full local checkout before merge.

Linked issues

This PR changes only the Vue nodes renderer. The classic litegraph canvas still resolves host previews from previewExposureStore alone (litegraphService.ts:193), and Comfy.VueNodes.Enabled defaults to false for OSS local installs, so classic-canvas parity is the other half of this incident and is not addressed here.

Note: #14551 was closed in favour of this PR (same mechanism, this one better guarded). Its SamplerCustomAdvanced e2e fixture and spec still need porting here so the non-allowlisted node type stays pinned.


Generated by Claude Code

claude added 2 commits August 2, 2026 06:56
…or preview

Two KSamplers in one subgraph, only one wired into previewExposureStore
(the other simulates a node added after the subgraph's one-shot
auto-expose already ran). The unit test targets the not-yet-existing
useAmbientSubgraphPreviews composable; the e2e spec drives two live
b_preview WS frames at distinct interior execution IDs and asserts both
show on the collapsed SubgraphNode host.
…t exposures

Subgraph previews were 1:1 with previewExposureStore, so a second
interior node producing output concurrently (e.g. a 2nd KSampler with
no exposure) had nowhere to render and stayed invisible on the
collapsed host.

Add useAmbientSubgraphPreviews, a computed rollup that mirrors how
executionStore's nodeLocationProgressStates bubbles up progress
regardless of promotion: it reads each direct interior node's own
output/preview state from nodeOutputStore, independent of exposures.
LGraphNode.vue merges it underneath the existing exposure-driven
promotedPreviews (an explicit exposure still wins for that node), so
every concurrently-executing interior node shows and updates on its
own, not just the promoted one.
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

🎭 Playwright: ✅ 1989 passed, 0 failed · 1 flaky

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

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 08/23/2026, 08:09:28 AM UTC

Links

📦 Bundle: 9.11 MB gzip 🔴 +1.04 kB

Details

Summary

  • Raw size: 38.6 MB baseline 38.6 MB — 🔴 +3.62 kB
  • Gzip: 9.11 MB baseline 9.11 MB — 🔴 +1.04 kB
  • Brotli: 6.38 MB baseline 6.37 MB — 🔴 +890 B
  • Bundles: 438 current • 438 baseline • 4 added / 4 removed

Category Glance
Graph Workspace 🔴 +3.44 kB (1.38 MB) · Other 🔴 +183 B (14.1 MB) · Vendor & Third-Party ⚪ 0 B (18.1 MB) · Data & Services ⚪ 0 B (3.53 MB) · Panels & Settings ⚪ 0 B (591 kB) · Utilities & Hooks ⚪ 0 B (549 kB) · + 5 more

App Entry Points — 3.71 kB (baseline 3.71 kB) • ⚪ 0 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-B_P8RsYa.js (new) 3.71 kB 🔴 +3.71 kB 🔴 +1.86 kB 🔴 +1.62 kB
assets/index-CT3Qv84J.js (removed) 3.71 kB 🟢 -3.71 kB 🟢 -1.86 kB 🟢 -1.6 kB

Status: 1 added / 1 removed

Graph Workspace — 1.38 MB (baseline 1.38 MB) • 🔴 +3.44 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-7ot9nUYO.js (new) 1.37 MB 🔴 +1.37 MB 🔴 +300 kB 🔴 +226 kB
assets/GraphView-C49YWoR7.js (removed) 1.37 MB 🟢 -1.37 MB 🟢 -299 kB 🟢 -225 kB

Status: 1 added / 1 removed / 2 unchanged

Views & Navigation — 124 kB (baseline 124 kB) • ⚪ 0 B

Top-level views, pages, and routed surfaces

Status: 17 unchanged

Panels & Settings — 591 kB (baseline 591 kB) • ⚪ 0 B

Configuration panels, inspectors, and settings screens

Status: 27 unchanged

User & Accounts — 27.5 kB (baseline 27.5 kB) • ⚪ 0 B

Authentication, profile, and account management bundles

Status: 11 unchanged

Editors & Dialogs — 125 kB (baseline 125 kB) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

Status: 8 unchanged

UI Components — 67.1 kB (baseline 67.1 kB) • ⚪ 0 B

Reusable component library chunks

Status: 14 unchanged

Data & Services — 3.53 MB (baseline 3.53 MB) • ⚪ 0 B

Stores, services, APIs, and repositories

Status: 17 unchanged

Utilities & Hooks — 549 kB (baseline 549 kB) • ⚪ 0 B

Helpers, composables, and utility bundles

Status: 37 unchanged

Vendor & Third-Party — 18.1 MB (baseline 18.1 MB) • ⚪ 0 B

External libraries and shared vendor chunks

Status: 18 unchanged

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

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/main-Bpsl3U5o.js (new) 47.4 kB 🔴 +47.4 kB 🔴 +13.7 kB 🔴 +11.9 kB
assets/main-Zt7EuxFU.js (removed) 47.4 kB 🟢 -47.4 kB 🟢 -13.7 kB 🟢 -11.9 kB
assets/CurrentUserPopoverWorkspace-DpXxWYjA.js (new) 15.9 kB 🔴 +15.9 kB 🔴 +3.86 kB 🔴 +3.4 kB
assets/CurrentUserPopoverWorkspace-C6--ases.js (removed) 15.7 kB 🟢 -15.7 kB 🟢 -3.83 kB 🟢 -3.37 kB

Status: 2 added / 2 removed / 283 unchanged

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 63.7 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 56.7 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 62.3 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 68.5 MB heap
large-graph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.8 MB heap
large-graph-pan: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 77.7 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 83.0 MB heap
legacy-node-drag: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 61.0 MB heap
minimap-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 68.1 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 70.6 MB heap
subgraph-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.3 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.3 MB heap
subgraph-transition-enter: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 99ms TBT · 84.3 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 81.8 MB heap
vue-large-graph-idle: · 59.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 169.2 MB heap
vue-large-graph-pan: · 58.1 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 185.6 MB heap
workflow-execution: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 64.0 MB heap

⚠️ 1 regression detected

Show regressions
Metric Baseline PR (median) Δ Sig
workflow-execution: event listeners 25 98 +292% ⚠️ z=10.6
All metrics
Metric Baseline PR (median) Δ Sig
canvas-idle: avg frame time 17ms 17ms +0% z=-0.1
canvas-idle: p95 frame time 17ms 17ms +1%
canvas-idle: layout duration 0ms 0ms +0%
canvas-idle: style recalc duration 6ms 5ms -18% z=-6.3
canvas-idle: layout count 0 0 +0%
canvas-idle: style recalc count 8 10 +19% z=-2.9
canvas-idle: task duration 504ms 387ms -23% z=-0.2
canvas-idle: script duration 7ms 5ms -23% z=-9.0
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: heap used 60.9 MB 63.7 MB +5%
canvas-idle: DOM nodes -283 -282 -1% z=-238.4
canvas-idle: event listeners -151 -167 +11% z=-37.7
canvas-mouse-sweep: avg frame time 17ms 17ms -0% z=-0.9
canvas-mouse-sweep: p95 frame time 17ms 17ms +0%
canvas-mouse-sweep: layout duration 4ms 3ms -27% z=-4.5
canvas-mouse-sweep: style recalc duration 41ms 24ms -40% z=-5.8
canvas-mouse-sweep: layout count 12 12 +0%
canvas-mouse-sweep: style recalc count 75 72 -5% z=-3.1
canvas-mouse-sweep: task duration 920ms 660ms -28% z=-3.5
canvas-mouse-sweep: script duration 110ms 72ms -35% z=-9.9
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: heap used 57.6 MB 56.7 MB -2%
canvas-mouse-sweep: DOM nodes -280 -282 +1% z=-133.1
canvas-mouse-sweep: event listeners -151 -151 +0% z=-38.3
canvas-zoom-sweep: avg frame time 17ms 17ms +0% z=0.5
canvas-zoom-sweep: p95 frame time 17ms 17ms -1%
canvas-zoom-sweep: layout duration 1ms 1ms +1% z=-1.3
canvas-zoom-sweep: style recalc duration 15ms 11ms -27% z=-5.1
canvas-zoom-sweep: layout count 6 6 +0%
canvas-zoom-sweep: style recalc count 30 29 -3% z=-5.0
canvas-zoom-sweep: task duration 347ms 274ms -21% z=-2.3
canvas-zoom-sweep: script duration 9ms 8ms -12% z=-6.4
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: heap used 62.7 MB 62.3 MB -1%
canvas-zoom-sweep: DOM nodes 76 76 +0% z=-4.1
canvas-zoom-sweep: event listeners 19 19 +0% z=-0.9
dom-widget-clipping: avg frame time 17ms 17ms -0% z=-0.6
dom-widget-clipping: p95 frame time 17ms 17ms -0%
dom-widget-clipping: layout duration 0ms 0ms +0%
dom-widget-clipping: style recalc duration 7ms 6ms -20% z=-5.3
dom-widget-clipping: layout count 0 0 +0%
dom-widget-clipping: style recalc count 11 13 +14% z=-1.2
dom-widget-clipping: task duration 369ms 255ms -31% z=-6.7
dom-widget-clipping: script duration 55ms 34ms -37% z=-10.3
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: heap used 68.8 MB 68.5 MB -0%
dom-widget-clipping: DOM nodes 18 21 +17% z=-0.8
dom-widget-clipping: event listeners 2 0 -100% variance too high
large-graph-idle: avg frame time 17ms 17ms -0% z=-0.6
large-graph-idle: p95 frame time 17ms 17ms +0%
large-graph-idle: layout duration 0ms 0ms +0%
large-graph-idle: style recalc duration 7ms 6ms -3% z=-5.6
large-graph-idle: layout count 0 0 +0%
large-graph-idle: style recalc count 8 10 +19% z=-6.7
large-graph-idle: task duration 606ms 481ms -21% z=-1.1
large-graph-idle: script duration 13ms 13ms -6% z=-8.5
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: heap used 69.6 MB 65.8 MB -5%
large-graph-idle: DOM nodes -275 -274 -0% z=-330.5
large-graph-idle: event listeners -151 -163 +8% z=-31.0
large-graph-pan: avg frame time 17ms 17ms +0% z=-0.2
large-graph-pan: p95 frame time 17ms 17ms -0%
large-graph-pan: layout duration 0ms 0ms +0%
large-graph-pan: style recalc duration 15ms 12ms -18% z=-6.1
large-graph-pan: layout count 0 0 +0%
large-graph-pan: style recalc count 68 69 +1% z=-0.9
large-graph-pan: task duration 1157ms 894ms -23% z=-4.4
large-graph-pan: script duration 321ms 267ms -17% z=-7.1
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: heap used 71.6 MB 77.7 MB +8%
large-graph-pan: DOM nodes -279 -261 -6% z=-169.7
large-graph-pan: event listeners -147 -164 +12% z=-204.0
large-graph-zoom: avg frame time 17ms 17ms -0%
large-graph-zoom: p95 frame time 17ms 17ms +0%
large-graph-zoom: layout duration 7ms 8ms +4%
large-graph-zoom: style recalc duration 15ms 15ms -1%
large-graph-zoom: layout count 60 60 +0%
large-graph-zoom: style recalc count 65 66 +1%
large-graph-zoom: task duration 1338ms 1087ms -19%
large-graph-zoom: script duration 372ms 293ms -21%
large-graph-zoom: TBT 0ms 0ms +0%
large-graph-zoom: heap used 73.6 MB 83.0 MB +13%
large-graph-zoom: DOM nodes -273 -130 -53%
large-graph-zoom: event listeners -147 -69 -53%
legacy-node-drag: avg frame time 17ms 17ms +0%
legacy-node-drag: p95 frame time 17ms 17ms -0%
legacy-node-drag: layout duration 0ms 0ms +0%
legacy-node-drag: style recalc duration 10ms 10ms +2%
legacy-node-drag: layout count 0 0 +0%
legacy-node-drag: style recalc count 46 48 +3%
legacy-node-drag: task duration 1380ms 1124ms -19%
legacy-node-drag: script duration 445ms 343ms -23%
legacy-node-drag: TBT 0ms 0ms +0%
legacy-node-drag: heap used 66.5 MB 61.0 MB -8%
legacy-node-drag: DOM nodes 12 -243 -2125%
legacy-node-drag: event listeners 186 32 -83%
minimap-idle: avg frame time 17ms 17ms -0% z=-0.4
minimap-idle: p95 frame time 17ms 17ms -1%
minimap-idle: layout duration 0ms 0ms +0%
minimap-idle: style recalc duration 6ms 6ms -0% z=-4.0
minimap-idle: layout count 0 0 +0%
minimap-idle: style recalc count 8 8 +0% z=-2.3
minimap-idle: task duration 604ms 496ms -18% z=-0.7
minimap-idle: script duration 13ms 15ms +9% z=-8.4
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: heap used 70.7 MB 68.1 MB -4%
minimap-idle: DOM nodes -274 -281 +2% z=-219.1
minimap-idle: event listeners -147 -149 +1% z=-232.6
subgraph-dom-widget-clipping: avg frame time 17ms 17ms +0% z=0.1
subgraph-dom-widget-clipping: p95 frame time 17ms 17ms -1%
subgraph-dom-widget-clipping: layout duration 0ms 0ms +0%
subgraph-dom-widget-clipping: style recalc duration 9ms 8ms -5% z=-4.6
subgraph-dom-widget-clipping: layout count 0 0 +0%
subgraph-dom-widget-clipping: style recalc count 46 48 +3% z=-0.8
subgraph-dom-widget-clipping: task duration 374ms 310ms -17% z=-3.7
subgraph-dom-widget-clipping: script duration 115ms 90ms -22% z=-6.0
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: heap used 70.8 MB 70.6 MB -0%
subgraph-dom-widget-clipping: DOM nodes 18 21 +17% z=-1.1
subgraph-dom-widget-clipping: event listeners 8 6 -25% z=-1.7
subgraph-idle: avg frame time 17ms 17ms -0% z=-0.2
subgraph-idle: p95 frame time 17ms 17ms +0%
subgraph-idle: layout duration 0ms 0ms +0%
subgraph-idle: style recalc duration 7ms 6ms -18% z=-6.1
subgraph-idle: layout count 0 0 +0%
subgraph-idle: style recalc count 9 10 +6% z=-2.1
subgraph-idle: task duration 472ms 382ms -19% z=0.4
subgraph-idle: script duration 6ms 5ms -20% z=-5.7
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: heap used 63.6 MB 69.3 MB +9%
subgraph-idle: DOM nodes -281 -282 +0% z=-203.2
subgraph-idle: event listeners -151 -167 +11% variance too high
subgraph-mouse-sweep: avg frame time 17ms 17ms -0% z=-0.5
subgraph-mouse-sweep: p95 frame time 17ms 17ms -0%
subgraph-mouse-sweep: layout duration 4ms 3ms -17% z=-4.2
subgraph-mouse-sweep: style recalc duration 33ms 25ms -23% z=-5.1
subgraph-mouse-sweep: layout count 16 16 +0%
subgraph-mouse-sweep: style recalc count 75 75 +0% z=-2.7
subgraph-mouse-sweep: task duration 760ms 618ms -19% z=-2.1
subgraph-mouse-sweep: script duration 77ms 57ms -26% z=-6.7
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: heap used 63.4 MB 53.3 MB -16%
subgraph-mouse-sweep: DOM nodes -281 -280 -0% z=-155.3
subgraph-mouse-sweep: event listeners -183 -151 -17% variance too high
subgraph-transition-enter: avg frame time 17ms 17ms -0%
subgraph-transition-enter: p95 frame time 17ms 17ms -1%
subgraph-transition-enter: layout duration 13ms 9ms -29%
subgraph-transition-enter: style recalc duration 29ms 24ms -18%
subgraph-transition-enter: layout count 15 15 +0%
subgraph-transition-enter: style recalc count 20 20 +0%
subgraph-transition-enter: task duration 874ms 664ms -24%
subgraph-transition-enter: script duration 15ms 12ms -21%
subgraph-transition-enter: TBT 117ms 99ms -15%
subgraph-transition-enter: heap used 92.0 MB 84.3 MB -8%
subgraph-transition-enter: DOM nodes 13673 13673 +0%
subgraph-transition-enter: event listeners 2375 2373 -0%
viewport-pan-sweep: avg frame time 17ms 17ms +0%
viewport-pan-sweep: p95 frame time 17ms 17ms +0%
viewport-pan-sweep: layout duration 0ms 0ms +0%
viewport-pan-sweep: style recalc duration 40ms 36ms -10%
viewport-pan-sweep: layout count 0 0 +0%
viewport-pan-sweep: style recalc count 252 250 -1%
viewport-pan-sweep: task duration 3884ms 3169ms -18%
viewport-pan-sweep: script duration 954ms 783ms -18%
viewport-pan-sweep: TBT 0ms 0ms +0%
viewport-pan-sweep: heap used 76.0 MB 81.8 MB +8%
viewport-pan-sweep: DOM nodes -239 -258 +8%
viewport-pan-sweep: event listeners -131 -148 +13%
vue-large-graph-idle: avg frame time 18ms 17ms -5%
vue-large-graph-idle: p95 frame time 17ms 17ms -0%
vue-large-graph-idle: layout duration 0ms 0ms +0%
vue-large-graph-idle: style recalc duration 0ms 0ms +0%
vue-large-graph-idle: layout count 0 0 +0%
vue-large-graph-idle: style recalc count 0 0 +0%
vue-large-graph-idle: task duration 16738ms 11311ms -32%
vue-large-graph-idle: script duration 106ms 84ms -21%
vue-large-graph-idle: TBT 0ms 0ms +0%
vue-large-graph-idle: heap used 172.5 MB 169.2 MB -2%
vue-large-graph-idle: DOM nodes -8312 -8312 +0%
vue-large-graph-idle: event listeners -16391 -16394 +0%
vue-large-graph-pan: avg frame time 18ms 17ms -3%
vue-large-graph-pan: p95 frame time 17ms 17ms -0%
vue-large-graph-pan: layout duration 0ms 0ms +0%
vue-large-graph-pan: style recalc duration 21ms 15ms -29%
vue-large-graph-pan: layout count 0 0 +0%
vue-large-graph-pan: style recalc count 173 78 -55%
vue-large-graph-pan: task duration 20678ms 14330ms -31%
vue-large-graph-pan: script duration 472ms 375ms -21%
vue-large-graph-pan: TBT 123ms 0ms -100%
vue-large-graph-pan: heap used 177.8 MB 185.6 MB +4%
vue-large-graph-pan: DOM nodes -8312 -8312 +0%
vue-large-graph-pan: event listeners -16385 -16389 +0%
workflow-execution: avg frame time 17ms 17ms +0% z=1.1
workflow-execution: p95 frame time 17ms 17ms -0%
workflow-execution: layout duration 1ms 1ms +42% z=-2.3
workflow-execution: style recalc duration 13ms 16ms +25% z=-3.6
workflow-execution: layout count 2 4 +75% z=-2.7
workflow-execution: style recalc count 7 21 +193% z=1.3
workflow-execution: task duration 71ms 100ms +40% z=-2.1
workflow-execution: script duration 4ms 8ms +79% z=-7.3
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: heap used 61.7 MB 64.0 MB +4%
workflow-execution: DOM nodes 126 151 +19% z=-1.4
workflow-execution: event listeners 25 98 +292% ⚠️ z=10.6
Historical variance (last 15 runs)
Metric μ σ CV
canvas-idle: avg frame time 17ms 0ms 0.0%
canvas-idle: layout duration 0ms 0ms 0.0%
canvas-idle: style recalc duration 11ms 1ms 8.2%
canvas-idle: layout count 0 0 0.0%
canvas-idle: style recalc count 11 1 5.0%
canvas-idle: task duration 395ms 31ms 7.9%
canvas-idle: script duration 25ms 2ms 8.8%
canvas-idle: TBT 0ms 0ms 0.0%
canvas-idle: DOM nodes 23 1 5.6%
canvas-idle: event listeners 12 5 40.9%
canvas-mouse-sweep: avg frame time 17ms 0ms 0.0%
canvas-mouse-sweep: layout duration 4ms 0ms 5.4%
canvas-mouse-sweep: style recalc duration 43ms 3ms 7.4%
canvas-mouse-sweep: layout count 12 0 0.0%
canvas-mouse-sweep: style recalc count 79 2 3.0%
canvas-mouse-sweep: task duration 865ms 58ms 6.7%
canvas-mouse-sweep: script duration 136ms 6ms 4.8%
canvas-mouse-sweep: TBT 0ms 0ms 0.0%
canvas-mouse-sweep: DOM nodes 62 3 4.2%
canvas-mouse-sweep: event listeners 8 4 49.4%
canvas-zoom-sweep: avg frame time 17ms 0ms 0.0%
canvas-zoom-sweep: layout duration 1ms 0ms 7.0%
canvas-zoom-sweep: style recalc duration 19ms 2ms 8.0%
canvas-zoom-sweep: layout count 6 0 0.0%
canvas-zoom-sweep: style recalc count 31 0 1.5%
canvas-zoom-sweep: task duration 327ms 23ms 7.1%
canvas-zoom-sweep: script duration 27ms 3ms 11.1%
canvas-zoom-sweep: TBT 0ms 0ms 0.0%
canvas-zoom-sweep: DOM nodes 79 1 1.0%
canvas-zoom-sweep: event listeners 24 5 21.8%
dom-widget-clipping: avg frame time 17ms 0ms 0.0%
dom-widget-clipping: layout duration 0ms 0ms 0.0%
dom-widget-clipping: style recalc duration 10ms 1ms 8.0%
dom-widget-clipping: layout count 0 0 0.0%
dom-widget-clipping: style recalc count 13 0 3.8%
dom-widget-clipping: task duration 365ms 16ms 4.5%
dom-widget-clipping: script duration 68ms 3ms 4.8%
dom-widget-clipping: TBT 0ms 0ms 0.0%
dom-widget-clipping: DOM nodes 22 1 6.4%
dom-widget-clipping: event listeners 8 6 81.2%
large-graph-idle: avg frame time 17ms 0ms 0.0%
large-graph-idle: layout duration 0ms 0ms 0.0%
large-graph-idle: style recalc duration 12ms 1ms 8.6%
large-graph-idle: layout count 0 0 0.0%
large-graph-idle: style recalc count 12 0 2.7%
large-graph-idle: task duration 542ms 54ms 10.0%
large-graph-idle: script duration 102ms 11ms 10.3%
large-graph-idle: TBT 0ms 0ms 0.0%
large-graph-idle: DOM nodes 25 1 3.7%
large-graph-idle: event listeners 26 6 23.2%
large-graph-pan: avg frame time 17ms 0ms 0.0%
large-graph-pan: layout duration 0ms 0ms 0.0%
large-graph-pan: style recalc duration 17ms 1ms 4.6%
large-graph-pan: layout count 0 0 0.0%
large-graph-pan: style recalc count 70 1 0.9%
large-graph-pan: task duration 1082ms 43ms 4.0%
large-graph-pan: script duration 408ms 20ms 4.8%
large-graph-pan: TBT 0ms 0ms 0.0%
large-graph-pan: DOM nodes 19 2 8.7%
large-graph-pan: event listeners 5 1 16.8%
minimap-idle: avg frame time 17ms 0ms 0.0%
minimap-idle: layout duration 0ms 0ms 0.0%
minimap-idle: style recalc duration 10ms 1ms 8.6%
minimap-idle: layout count 0 0 0.0%
minimap-idle: style recalc count 10 1 7.1%
minimap-idle: task duration 527ms 47ms 9.0%
minimap-idle: script duration 98ms 10ms 10.1%
minimap-idle: TBT 0ms 0ms 0.0%
minimap-idle: DOM nodes 19 1 7.1%
minimap-idle: event listeners 5 1 14.4%
subgraph-dom-widget-clipping: avg frame time 17ms 0ms 0.0%
subgraph-dom-widget-clipping: layout duration 0ms 0ms 0.0%
subgraph-dom-widget-clipping: style recalc duration 13ms 1ms 7.4%
subgraph-dom-widget-clipping: layout count 0 0 0.0%
subgraph-dom-widget-clipping: style recalc count 48 1 1.2%
subgraph-dom-widget-clipping: task duration 378ms 18ms 4.9%
subgraph-dom-widget-clipping: script duration 128ms 6ms 4.9%
subgraph-dom-widget-clipping: TBT 0ms 0ms 0.0%
subgraph-dom-widget-clipping: DOM nodes 22 1 5.0%
subgraph-dom-widget-clipping: event listeners 16 6 36.0%
subgraph-idle: avg frame time 17ms 0ms 0.0%
subgraph-idle: layout duration 0ms 0ms 0.0%
subgraph-idle: style recalc duration 10ms 1ms 7.5%
subgraph-idle: layout count 0 0 0.0%
subgraph-idle: style recalc count 11 1 6.0%
subgraph-idle: task duration 370ms 31ms 8.5%
subgraph-idle: script duration 20ms 3ms 13.2%
subgraph-idle: TBT 0ms 0ms 0.0%
subgraph-idle: DOM nodes 22 1 6.9%
subgraph-idle: event listeners 10 7 64.5%
subgraph-mouse-sweep: avg frame time 17ms 0ms 0.0%
subgraph-mouse-sweep: layout duration 5ms 0ms 6.8%
subgraph-mouse-sweep: style recalc duration 42ms 3ms 7.8%
subgraph-mouse-sweep: layout count 16 0 0.0%
subgraph-mouse-sweep: style recalc count 80 2 2.4%
subgraph-mouse-sweep: task duration 766ms 69ms 9.0%
subgraph-mouse-sweep: script duration 101ms 7ms 6.5%
subgraph-mouse-sweep: TBT 0ms 0ms 0.0%
subgraph-mouse-sweep: DOM nodes 67 2 3.3%
subgraph-mouse-sweep: event listeners 8 4 52.6%
workflow-execution: avg frame time 17ms 0ms 0.0%
workflow-execution: layout duration 2ms 0ms 9.4%
workflow-execution: style recalc duration 24ms 2ms 9.1%
workflow-execution: layout count 5 1 11.0%
workflow-execution: style recalc count 18 2 11.5%
workflow-execution: task duration 123ms 11ms 8.8%
workflow-execution: script duration 29ms 3ms 10.2%
workflow-execution: TBT 0ms 0ms 0.0%
workflow-execution: DOM nodes 161 7 4.4%
workflow-execution: event listeners 52 4 8.4%
Trend (last 15 commits on main)
Metric Trend Dir Latest
canvas-idle: avg frame time ▆▃▆▁▆▃▆█▆▆▄▃▃▄▃ ➡️ 17ms
canvas-idle: p95 frame time ➡️ NaNms
canvas-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: style recalc duration ▇▇▆▆▃█▄▃▄▃▇▄▁▆▇ ➡️ 11ms
canvas-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
canvas-idle: style recalc count █▃▅▂▅▆▃▁▂▁▂▅▆▅▆ ➡️ 12
canvas-idle: task duration ▃▃▃▆▂▃▃▅▆▂█▃▁▃▃ ➡️ 391ms
canvas-idle: script duration ▄▃▅▇▂▅▃▆▇▅█▄▁▅▆ ➡️ 27ms
canvas-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: heap used ➡️ NaN MB
canvas-idle: DOM nodes █▇▆▅▃▇▃▁▂▂▅▆▆▆▇ ➡️ 24
canvas-idle: event listeners ▅█▅▄▁▅▁▁▁▄▅▅▁▅▄ 📉 11
canvas-mouse-sweep: avg frame time ▆█▆▃▁▃▁▆▆▁▃▆▆▃▃ ➡️ 17ms
canvas-mouse-sweep: p95 frame time ➡️ NaNms
canvas-mouse-sweep: layout duration ▁▃▂▄▁▂▁▃▆▂█▇▆▄▃ ➡️ 4ms
canvas-mouse-sweep: style recalc duration ▄▄▂▄▁▂▃▃▅▄█▆▂▄▄ ➡️ 43ms
canvas-mouse-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 12
canvas-mouse-sweep: style recalc count █▅▄▃▂▂▁▄▄▅▆▅▂▇▄ ➡️ 79
canvas-mouse-sweep: task duration █▆▄▂▂▃▂▄▄▅█▆▁▆▄ ➡️ 868ms
canvas-mouse-sweep: script duration ▄▅▄▆▄▆▆▆▅▅█▆▁▅▆ ➡️ 139ms
canvas-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-mouse-sweep: heap used ➡️ NaN MB
canvas-mouse-sweep: DOM nodes █▅▃▃▁▂▂▃▂▄▆▅▃▅▅ ➡️ 64
canvas-mouse-sweep: event listeners █▁▁▁▁▁▇▁▁▁██▇▁█ 📈 13
canvas-zoom-sweep: avg frame time ▅▅█▄▅▁▁▁▅▁▁▅▄▅▁ ➡️ 17ms
canvas-zoom-sweep: p95 frame time ➡️ NaNms
canvas-zoom-sweep: layout duration ▆▅▅▄▁▁█▅▃▅▇▆▁▂▆ ➡️ 1ms
canvas-zoom-sweep: style recalc duration ▆▅▄▆▅▃█▆▇▅▇▄▁▃▅ ➡️ 20ms
canvas-zoom-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 6
canvas-zoom-sweep: style recalc count ▁▁▃▄▆▃▆█▄▄▆▁▆▁▆ ➡️ 32
canvas-zoom-sweep: task duration ▄▂▁▇▂▂▄▅▆▃█▄▁▁▅ ➡️ 338ms
canvas-zoom-sweep: script duration ▃▃▂▇▂▂▅▇▆▅█▄▁▂▆ ➡️ 30ms
canvas-zoom-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-zoom-sweep: heap used ➡️ NaN MB
canvas-zoom-sweep: DOM nodes ▄▃▁▅█▁▃▆▄▅▅▃▃▄▃ ➡️ 79
canvas-zoom-sweep: event listeners ▁▁▂▅█▂▁▅▁▅▅▄▁▅▁ ➡️ 19
dom-widget-clipping: avg frame time ▂▄▅▅▂▄█▇▅▇▇▅▅▁▇ ➡️ 17ms
dom-widget-clipping: p95 frame time ➡️ NaNms
dom-widget-clipping: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: style recalc duration ▆▆▂▆▄▃██▄▁▆▇▆▃▅ ➡️ 10ms
dom-widget-clipping: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
dom-widget-clipping: style recalc count ▇█▅█▅▄█▇▇▁▇▄▇▂▅ ➡️ 13
dom-widget-clipping: task duration ▃▃▁▅▄▃▅▆▅▂▇█▁▅▅ ➡️ 371ms
dom-widget-clipping: script duration ▅▄▄▆▆▅▇▇▆▃█▇▁▇▇ ➡️ 71ms
dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: heap used ➡️ NaN MB
dom-widget-clipping: DOM nodes ▇▇▄▇▅▄█▇▅▁▅▄▇▃▄ ➡️ 21
dom-widget-clipping: event listeners ▅▅▅▅▁▅██▁▁▁▁█▁▁ 📉 2
large-graph-idle: avg frame time ▅▅▅▅▅▂▁▂▄▅▄▂▂▅█ ➡️ 17ms
large-graph-idle: p95 frame time ➡️ NaNms
large-graph-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: style recalc duration ▅▅▅▆▄▅▃▄▅▅▆█▁▄▆ ➡️ 13ms
large-graph-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-idle: style recalc count █▆█▃▃▁▃▆▃▆▆▃▆██ ➡️ 12
large-graph-idle: task duration ▂▃▂▆▂▃▃▇▅▃██▁▂▅ ➡️ 569ms
large-graph-idle: script duration ▄▅▄▆▄▅▅▇▆▅█▆▁▃▆ ➡️ 110ms
large-graph-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: heap used ➡️ NaN MB
large-graph-idle: DOM nodes ▆█▅▂▅▃▁▂▃▅▅▆▂▆▅ ➡️ 25
large-graph-idle: event listeners ███▇██▄▁▄▇▇█▂█▇ ➡️ 29
large-graph-pan: avg frame time ▆▃▃▆█▃▁█▆▆▆▆█▁▆ ➡️ 17ms
large-graph-pan: p95 frame time ➡️ NaNms
large-graph-pan: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: style recalc duration ▃▂▄▄▁▅▂▂▁▄▄█▃▁▂ ➡️ 17ms
large-graph-pan: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-pan: style recalc count ▆▃█▂▃▂▂▂▁▇▅▃█▆▃ ➡️ 69
large-graph-pan: task duration ▄▃▄▆▄▄▄▆▄▄█▆▁▂▅ ➡️ 1100ms
large-graph-pan: script duration ▅▄▅▆▆▅▄▆▄▅█▄▁▄▅ ➡️ 413ms
large-graph-pan: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: heap used ➡️ NaN MB
large-graph-pan: DOM nodes ▅▃▆▂▄▁▃▁▁▅▁▂█▅▂ ➡️ 18
large-graph-pan: event listeners █▆█▁▁▆▁▁▃▆▁▃██▃ ➡️ 5
minimap-idle: avg frame time ▃▆▆▃█▁█▆▆▃▃▆█▆█ ➡️ 17ms
minimap-idle: p95 frame time ➡️ NaNms
minimap-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: style recalc duration ▄█▁█▅▅█▅▅▃▅▁▁▄▆ ➡️ 10ms
minimap-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
minimap-idle: style recalc count ▃▅▂▄█▃▆▁▂▅▂▁▅▆▃ ➡️ 9
minimap-idle: task duration ▃▄▁▅▁▃▄▅▇▃█▅▁▁▅ ➡️ 547ms
minimap-idle: script duration ▄▆▃▇▃▅▆▆▇▅█▅▁▃▆ ➡️ 106ms
minimap-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: heap used ➡️ NaN MB
minimap-idle: DOM nodes ▃▅▂▄█▃▆▁▂▅▂▁▅▆▃ ➡️ 19
minimap-idle: event listeners ▃▃▆▁▁▁▃▁▁▆▁▃█▆▁ ➡️ 4
subgraph-dom-widget-clipping: avg frame time ▅▄▄▄▄▄█▄▄▄▃▁▆▃▃ ➡️ 17ms
subgraph-dom-widget-clipping: p95 frame time ➡️ NaNms
subgraph-dom-widget-clipping: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: style recalc duration ▂▄▃▅▅▃▂▅▇▃▄█▁▄▆ ➡️ 14ms
subgraph-dom-widget-clipping: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-dom-widget-clipping: style recalc count ▇█▆▃▆▃▁▆█▇▃▆▇█▅ ➡️ 48
subgraph-dom-widget-clipping: task duration ▂▃▃▆▅▅▂▅█▂▆█▁▂▇ ➡️ 398ms
subgraph-dom-widget-clipping: script duration ▃▃▃▄▅▅▂▄█▂▅▇▁▂▅ ➡️ 131ms
subgraph-dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: heap used ➡️ NaN MB
subgraph-dom-widget-clipping: DOM nodes ▅▇▅▂▅▂▁▅▅▅▁▇▅█▄ ➡️ 22
subgraph-dom-widget-clipping: event listeners ▅▅▅▂▅▁▅██▁▁█▅█▅ 📈 16
subgraph-idle: avg frame time ▆▆█▁▆▃▆▆▆▃▆▁▃▆█ ➡️ 17ms
subgraph-idle: p95 frame time ➡️ NaNms
subgraph-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: style recalc duration ▁▇▃▆▂▄▂▃▃▆▆▄▃▇█ ➡️ 12ms
subgraph-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-idle: style recalc count ▃▆▃▃▂▅▁▂▁▆▃▃██▇ ➡️ 12
subgraph-idle: task duration ▁▃▁▇▁▁▃▆▅▂█▅▁▁▄ ➡️ 378ms
subgraph-idle: script duration ▁▃▂▇▁▂▃▇▆▂█▅▂▁▅ ➡️ 22ms
subgraph-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: heap used ➡️ NaN MB
subgraph-idle: DOM nodes ▃▅▃▂▁▄▁▂▁▅▃▂▇█▇ ➡️ 24
subgraph-idle: event listeners ▁▅▁▁▁▁▁▁▁▅▄▁███ 📈 21
subgraph-mouse-sweep: avg frame time ▅▄▁▃▃▄▆▄▆▃▃█▁▃▃ ➡️ 17ms
subgraph-mouse-sweep: p95 frame time ➡️ NaNms
subgraph-mouse-sweep: layout duration ▁▄▄▄▃▃▅▅▅▂█▇▂▃▆ ➡️ 5ms
subgraph-mouse-sweep: style recalc duration ▃▂▄▅▂▃▄▅█▃█▆▁▂▅ ➡️ 43ms
subgraph-mouse-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 16
subgraph-mouse-sweep: style recalc count ▅▂▅▅▁▄▃▅█▅▆▄▂▄▅ ➡️ 81
subgraph-mouse-sweep: task duration ▃▂▄▅▂▄▄▅▇▄█▆▁▃▅ ➡️ 785ms
subgraph-mouse-sweep: script duration ▄▅▄▇▅▅▆▇▆▅██▁▄▆ ➡️ 105ms
subgraph-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-mouse-sweep: heap used ➡️ NaN MB
subgraph-mouse-sweep: DOM nodes ▅▁▄▅▁▄▃▃█▅▅▄▂▅▃ ➡️ 66
subgraph-mouse-sweep: event listeners ▇▁▂▇▁▂▂▂█▇▂▂▇▇▂ 📈 5
workflow-execution: avg frame time ▆▆▆▄▆▆▃▄▁▄█▆▅▄▆ ➡️ 17ms
workflow-execution: p95 frame time ➡️ NaNms
workflow-execution: layout duration ▁▆▁▃▂▄▃▂▃▃▅█▄▂▅ ➡️ 2ms
workflow-execution: style recalc duration ▃▇▅▇▁▅▆▇█▁██▂▄▆ ➡️ 25ms
workflow-execution: layout count ▁█▂▃▂▃▃▁▃▃▄▃▂▃▂ ➡️ 5
workflow-execution: style recalc count ▃█▅▇▁▄▅▆▅▅▅▅▄▄▂ ➡️ 15
workflow-execution: task duration ▂▅▄▅▁▄▆▆▆▁▇█▁▃▃ ➡️ 120ms
workflow-execution: script duration ▄▃▄▄▃▅▄▅▆▂▇█▁▃▄ ➡️ 29ms
workflow-execution: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
workflow-execution: heap used ➡️ NaN MB
workflow-execution: DOM nodes ▂█▃▆▁▄▃▅▃█▃▃▄▃▁ ➡️ 152
workflow-execution: event listeners ▅███▁▅███▁██▅█▅ ➡️ 49
Raw data
{
  "timestamp": "2026-08-23T08:19:03.141Z",
  "gitSha": "37218003baa53910a2f6516210ff9ed58a37f5d7",
  "branch": "fix/decouple-subgraph-preview-outputs",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2001.0839999999916,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 5.047,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 394.05199999999996,
      "heapDeltaBytes": 6821972,
      "heapUsedBytes": 68828492,
      "domNodes": -282,
      "jsHeapTotalBytes": 4448256,
      "scriptDurationMs": 5.1709999999999985,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-idle",
      "durationMs": 2052.993000000015,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 5.421,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 379.802,
      "heapDeltaBytes": 2855652,
      "heapUsedBytes": 64807512,
      "domNodes": -281,
      "jsHeapTotalBytes": 3661824,
      "scriptDurationMs": 5.387000000000001,
      "eventListeners": -183,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1742.1769999999697,
      "styleRecalcs": 72,
      "styleRecalcDurationMs": 25.522,
      "layouts": 12,
      "layoutDurationMs": 2.641,
      "taskDurationMs": 685.917,
      "heapDeltaBytes": -3662152,
      "heapUsedBytes": 58848036,
      "domNodes": -282,
      "jsHeapTotalBytes": 4710400,
      "scriptDurationMs": 72.95100000000001,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1700.3479999999627,
      "styleRecalcs": 71,
      "styleRecalcDurationMs": 22.875,
      "layouts": 12,
      "layoutDurationMs": 2.818,
      "taskDurationMs": 633.1719999999999,
      "heapDeltaBytes": -2029800,
      "heapUsedBytes": 60089492,
      "domNodes": -282,
      "jsHeapTotalBytes": 4186112,
      "scriptDurationMs": 70.15599999999999,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1666.565999999989,
      "styleRecalcs": 29,
      "styleRecalcDurationMs": 12.439,
      "layouts": 6,
      "layoutDurationMs": 0.6300000000000001,
      "taskDurationMs": 283.839,
      "heapDeltaBytes": 2633012,
      "heapUsedBytes": 65336104,
      "domNodes": 76,
      "jsHeapTotalBytes": 4194304,
      "scriptDurationMs": 8.858,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1666.291000000001,
      "styleRecalcs": 29,
      "styleRecalcDurationMs": 10.301000000000002,
      "layouts": 6,
      "layoutDurationMs": 0.535,
      "taskDurationMs": 263.667,
      "heapDeltaBytes": 2945588,
      "heapUsedBytes": 65355940,
      "domNodes": 76,
      "jsHeapTotalBytes": 4718592,
      "scriptDurationMs": 7.055000000000001,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 436.56399999997575,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 5.7189999999999985,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 254.72100000000003,
      "heapDeltaBytes": 9549580,
      "heapUsedBytes": 72012260,
      "domNodes": 22,
      "jsHeapTotalBytes": 5242880,
      "scriptDurationMs": 34.043,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 428.99899999997615,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 5.613999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 254.703,
      "heapDeltaBytes": 9680688,
      "heapUsedBytes": 71729172,
      "domNodes": 20,
      "jsHeapTotalBytes": 5242880,
      "scriptDurationMs": 34.923,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2012.399999999957,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 5.190000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 472.43600000000004,
      "heapDeltaBytes": -13365920,
      "heapUsedBytes": 62777340,
      "domNodes": -280,
      "jsHeapTotalBytes": 4452352,
      "scriptDurationMs": 13.028999999999998,
      "eventListeners": -179,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2023.8750000000891,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 7.616,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 489.159,
      "heapDeltaBytes": -1380208,
      "heapUsedBytes": 75263612,
      "domNodes": -268,
      "jsHeapTotalBytes": -1576960,
      "scriptDurationMs": 12.188000000000004,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2111.013000000071,
      "styleRecalcs": 70,
      "styleRecalcDurationMs": 12.924000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 876.5049999999999,
      "heapDeltaBytes": -2429656,
      "heapUsedBytes": 74835100,
      "domNodes": -238,
      "jsHeapTotalBytes": -1089536,
      "scriptDurationMs": 291.839,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2078.2110000000102,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 11.873999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 910.6179999999999,
      "heapDeltaBytes": 11489440,
      "heapUsedBytes": 88032084,
      "domNodes": -284,
      "jsHeapTotalBytes": 4677632,
      "scriptDurationMs": 242.37800000000001,
      "eventListeners": -179,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3093.9260000000104,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 14.745999999999999,
      "layouts": 60,
      "layoutDurationMs": 8.248999999999999,
      "taskDurationMs": 1113.785,
      "heapDeltaBytes": 17814588,
      "heapUsedBytes": 96722520,
      "domNodes": 12,
      "jsHeapTotalBytes": 6328320,
      "scriptDurationMs": 305.026,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3055.511000000024,
      "styleRecalcs": 66,
      "styleRecalcDurationMs": 14.256999999999996,
      "layouts": 60,
      "layoutDurationMs": 7.257,
      "taskDurationMs": 1060.6299999999999,
      "heapDeltaBytes": -1736260,
      "heapUsedBytes": 77252336,
      "domNodes": -271,
      "jsHeapTotalBytes": -2363392,
      "scriptDurationMs": 281.45799999999997,
      "eventListeners": -145,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "legacy-node-drag",
      "durationMs": 2144.916999999964,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 9.814,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1140.9099999999999,
      "heapDeltaBytes": -23370852,
      "heapUsedBytes": 63829192,
      "domNodes": -244,
      "jsHeapTotalBytes": 4976640,
      "scriptDurationMs": 345.281,
      "eventListeners": 33,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "legacy-node-drag",
      "durationMs": 2131.799000000001,
      "styleRecalcs": 48,
      "styleRecalcDurationMs": 9.811999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1107.713,
      "heapDeltaBytes": -22667556,
      "heapUsedBytes": 64082352,
      "domNodes": -242,
      "jsHeapTotalBytes": 3665920,
      "scriptDurationMs": 339.88599999999997,
      "eventListeners": 31,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 2049.74500000003,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.308999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 526.408,
      "heapDeltaBytes": -12133600,
      "heapUsedBytes": 72594956,
      "domNodes": -277,
      "jsHeapTotalBytes": 2617344,
      "scriptDurationMs": 18.698000000000004,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2052.632000000017,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.170999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 464.667,
      "heapDeltaBytes": -12839068,
      "heapUsedBytes": 70287728,
      "domNodes": -284,
      "jsHeapTotalBytes": 3403776,
      "scriptDurationMs": 10.602999999999998,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 532.8499999999394,
      "styleRecalcs": 48,
      "styleRecalcDurationMs": 9.153999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 318.976,
      "heapDeltaBytes": 11516912,
      "heapUsedBytes": 74217588,
      "domNodes": 22,
      "jsHeapTotalBytes": 5242880,
      "scriptDurationMs": 92.759,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666682,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 508.27700000002096,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 7.527000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 300.75100000000003,
      "heapDeltaBytes": 11524248,
      "heapUsedBytes": 73789496,
      "domNodes": 20,
      "jsHeapTotalBytes": 4718592,
      "scriptDurationMs": 87.92099999999999,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2009.510999999975,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 5.763000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 384.14699999999993,
      "heapDeltaBytes": 2191756,
      "heapUsedBytes": 65033008,
      "domNodes": -282,
      "jsHeapTotalBytes": 4186112,
      "scriptDurationMs": 5.063999999999999,
      "eventListeners": -183,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1992.3539999999775,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 5.552,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 380.24399999999997,
      "heapDeltaBytes": 17835140,
      "heapUsedBytes": 80374236,
      "domNodes": -282,
      "jsHeapTotalBytes": 4448256,
      "scriptDurationMs": 5.073000000000001,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1692.9450000000088,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 26.255999999999993,
      "layouts": 16,
      "layoutDurationMs": 3.522,
      "taskDurationMs": 608.107,
      "heapDeltaBytes": -5646752,
      "heapUsedBytes": 56078788,
      "domNodes": -280,
      "jsHeapTotalBytes": 5234688,
      "scriptDurationMs": 54.589999999999996,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1689.4750000000158,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 24.532999999999998,
      "layouts": 16,
      "layoutDurationMs": 3.175,
      "taskDurationMs": 628.185,
      "heapDeltaBytes": -6837952,
      "heapUsedBytes": 55625240,
      "domNodes": -280,
      "jsHeapTotalBytes": 4448256,
      "scriptDurationMs": 58.984,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "subgraph-transition-enter",
      "durationMs": 898.7060000000611,
      "styleRecalcs": 20,
      "styleRecalcDurationMs": 23.96700000000001,
      "layouts": 15,
      "layoutDurationMs": 9.228000000000002,
      "taskDurationMs": 663.538,
      "heapDeltaBytes": -5895900,
      "heapUsedBytes": 88438356,
      "domNodes": 13673,
      "jsHeapTotalBytes": 11796480,
      "scriptDurationMs": 11.643999999999995,
      "eventListeners": 2373,
      "totalBlockingTimeMs": 99,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8098.973999999998,
      "styleRecalcs": 249,
      "styleRecalcDurationMs": 37.816,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3235.4210000000003,
      "heapDeltaBytes": 15924040,
      "heapUsedBytes": 91636612,
      "domNodes": -282,
      "jsHeapTotalBytes": 5201920,
      "scriptDurationMs": 801.608,
      "eventListeners": -163,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8121.497999999974,
      "styleRecalcs": 251,
      "styleRecalcDurationMs": 34.84,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3102.152,
      "heapDeltaBytes": 2990440,
      "heapUsedBytes": 79963064,
      "domNodes": -234,
      "jsHeapTotalBytes": 1007616,
      "scriptDurationMs": 764.0790000000001,
      "eventListeners": -133,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.80000000000109
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 13568.864000000076,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12457.071000000002,
      "heapDeltaBytes": -48680068,
      "heapUsedBytes": 176209800,
      "domNodes": -8312,
      "jsHeapTotalBytes": -17866752,
      "scriptDurationMs": 103.925,
      "eventListeners": -16393,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333326,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 10816.919999999982,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 10164.797,
      "heapDeltaBytes": -47408556,
      "heapUsedBytes": 178724460,
      "domNodes": -8312,
      "jsHeapTotalBytes": -14954496,
      "scriptDurationMs": 63.98100000000001,
      "eventListeners": -16395,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.220000000000073,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 15471.435000000041,
      "styleRecalcs": 85,
      "styleRecalcDurationMs": 15.360999999999986,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 14706.85,
      "heapDeltaBytes": -16744212,
      "heapUsedBytes": 195931576,
      "domNodes": -8312,
      "jsHeapTotalBytes": -18198528,
      "scriptDurationMs": 385.872,
      "eventListeners": -16387,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.220000000000073,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 14646.376999999917,
      "styleRecalcs": 70,
      "styleRecalcDurationMs": 14.069999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 13952.967999999999,
      "heapDeltaBytes": -19211940,
      "heapUsedBytes": 193300952,
      "domNodes": -8312,
      "jsHeapTotalBytes": -16625664,
      "scriptDurationMs": 363.1569999999999,
      "eventListeners": -16391,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.220000000000073,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "workflow-execution",
      "durationMs": 453.81299999996827,
      "styleRecalcs": 21,
      "styleRecalcDurationMs": 17.962,
      "layouts": 5,
      "layoutDurationMs": 1.586,
      "taskDurationMs": 105.11099999999999,
      "heapDeltaBytes": 5307696,
      "heapUsedBytes": 67184204,
      "domNodes": 152,
      "jsHeapTotalBytes": 524288,
      "scriptDurationMs": 9.536999999999999,
      "eventListeners": 97,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666682,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "workflow-execution",
      "durationMs": 466.86899999986053,
      "styleRecalcs": 20,
      "styleRecalcDurationMs": 14.791000000000002,
      "layouts": 2,
      "layoutDurationMs": 0.846,
      "taskDurationMs": 94.16199999999999,
      "heapDeltaBytes": 5055688,
      "heapUsedBytes": 67011468,
      "domNodes": 149,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 5.514999999999999,
      "eventListeners": 99,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.669999999999998,
      "p95FrameDurationMs": 16.700000000000728
    }
  ]
}

@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.14286% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...erer/extensions/vueNodes/components/LGraphNode.vue 87.50% 1 Missing ⚠️
@@            Coverage Diff             @@
##             main   #14569      +/-   ##
==========================================
+ Coverage   81.32%   81.90%   +0.57%     
==========================================
  Files        1882     1890       +8     
  Lines      107571   107310     -261     
  Branches    33984    33000     -984     
==========================================
+ Hits        87483    87887     +404     
+ Misses      19727    19076     -651     
+ Partials      361      347      -14     
Flag Coverage Δ
e2e 67.45% <100.00%> (+0.72%) ⬆️
unit 73.55% <93.93%> (+0.99%) ⬆️

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

Files with missing lines Coverage Δ
src/composables/node/mergeSubgraphPreviews.ts 100.00% <100.00%> (ø)
src/composables/node/useAmbientSubgraphPreviews.ts 100.00% <100.00%> (ø)
src/composables/node/usePromotedPreviews.ts 100.00% <100.00%> (+3.37%) ⬆️
...erer/extensions/vueNodes/components/LGraphNode.vue 83.67% <87.50%> (-2.88%) ⬇️

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

claude Bot pushed a commit that referenced this pull request Aug 2, 2026
useAmbientSubgraphPreviews treated any interior node with images in
nodeOutputStore as a live preview candidate, including nodes whose only
"output" is an input-type reference (e.g. an unpromoted LoadImage node's
own selected file). That made every such node's thumbnail always visible
on the collapsed subgraph host regardless of promotion state, breaking
promotion exclusivity.

CI caught this: browser_tests/tests/subgraph/subgraphPromotion.spec.ts
"Can promote multiple previews" > "Promote both image previews" failed
consistently (all 4 attempts) on PR #14569 - after promoting only one of
two Load Image previews, the host showed 2 preview elements instead of 1,
because the unpromoted node's own file leaked in via the new ambient
rollup.

Export isInputPreviewOutput from nodeOutputStore and use it to skip
input-only outputs in the ambient computed, so only genuine live
execution results (or streaming preview frames) surface ambiently -
matching the feature's stated intent (a second concurrently-executing
node with no exposure), not static input previews.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FCL4Meqh8o8VYh9xV1h1Ky
@christian-byrne
christian-byrne marked this pull request as ready for review August 4, 2026 00:44
@christian-byrne
christian-byrne requested a review from a team August 4, 2026 00:44
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Aug 4, 2026
@dosubot

dosubot Bot commented Aug 4, 2026

Copy link
Copy Markdown

📄 Knowledge review

Dosu skipped reviewing this PR because your organization has used its 200 included credits for the month. Your usage will reset on 2026-09-01. To have Dosu review this PR before then, ask your organization admin to upgrade to a pro account.


Leave Feedback Ask Dosu about ComfyUI_frontend Add Dosu to your team

@claude

claude Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Ported the three artifacts from #14551 per Christian's closing comment (commit 42b7c92):

  • browser_tests/assets/nodes/sampler_custom_advanced_in_subgraph.json + browser_tests/tests/subgraph/subgraphPreviewAllowlistGap.spec.ts — carried over as-is; this PR's own e2e fixture uses two KSamplers (both allowlisted), so this is the only test that pins a non-allowlisted interior node type (SamplerCustomAdvanced) and would catch a regression if a type gate were reintroduced into the ambient path. Verified with playwright test --list (discovers correctly under @vue-nodes/@subgraph) and vue-tsc --project browser_tests/tsconfig.json.
  • The two incident-94 expected-fail regression tests pinning the still-open !lgraphNode?.isSubgraphNode() guard at LGraphNode.vue:172 — ported verbatim with their breadcrumb comments into LGraphNode.test.ts (it.fails) and imagePreview.spec.ts (test.fail()); neither existed there yet. LGraphNode.test.ts passes locally (14 passed, 1 expected fail).

pnpm typecheck, targeted oxlint --type-aware/eslint/oxfmt --check, and the touched vitest suites (LGraphNode.test.ts, useAmbientSubgraphPreviews.test.ts, usePromotedPreviews.test.ts, promotionUtils.test.ts) are all clean. Full Playwright execution wasn't run (no live backend in this sandbox) — same disclosed gap as this PR's own test plan.


Generated by Claude Code

Comment thread browser_tests/tests/subgraph/subgraphConcurrentPreviews.spec.ts Outdated
@tallestbydesign-ui

Copy link
Copy Markdown

Hey, that sounds like an elegant solution. Is there any news on when this could be merged ?

@christian-byrne christian-byrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-agent review: 10 reviewer agents, a verification gate over the compiled findings, and an adversarial cut pass. Re-verified against 62b52fd after the two commits that landed mid-review; the SamplerCustomAdvanced port from #14551 is here and appreciated, and the comment cleanup did not change any of the findings below.

One item that does not anchor to a line, and one correction of my own:

Scope. This changes the Vue node renderer only. The classic litegraph canvas resolves host previews from previewExposureStore alone (litegraphService.ts:193), and Comfy.VueNodes.Enabled is defaultValue: false with defaultsByInstallVersion: { '1.41.0': isCloud || isDesktop }, so OSS local installs are still on the unfixed path. Classic-canvas parity is the other half of this incident.

Correction, mine. I added Fixes #9859 to this PR body earlier and have since downgraded it. #9859 says verbatim "In LG mode, live previews ... can no longer be shown ... In nodes 2.0 it works" — it is a classic-mode report that this PR provably does not fix, and the keyword would have auto-closed it. #14597 does not state its renderer either, so it is now Refs as well. Worth confirming the renderer on both before closing anything.

Follow-ups worth their own PRs rather than changes here: previews are keyed by subgraph definition id throughout nodeOutputStore, so two host instances of one definition share previews (pre-existing, and not fixed by the change suggested below either); nested subgraph hosts never bubble to an outer host even though the ancestor fan-out at app.ts:861 already writes that data to the nested host's locator.

Comment thread src/composables/node/useAmbientSubgraphPreviews.ts Outdated
Comment thread src/renderer/extensions/vueNodes/components/LGraphNode.vue Outdated
Comment thread src/renderer/extensions/vueNodes/components/LGraphNode.vue
Comment thread src/composables/node/useAmbientSubgraphPreviews.test.ts
Comment thread browser_tests/tests/subgraph/subgraphConcurrentPreviews.spec.ts
Comment thread src/composables/node/useAmbientSubgraphPreviews.ts
Comment thread src/composables/node/useAmbientSubgraphPreviews.ts
Comment thread src/renderer/extensions/vueNodes/components/LGraphNode.test.ts
@claude

claude Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all 8 line-level findings from the latest review round:

  1. Id-namespace collision (LGraphNode.vue:728) — the exclusion set is now built from previewExposureStore.getExposures(rootGraphId, hostLocator) instead of promotedPreviews[].sourceNodeId, so both sides of the dedup are in the host's own id space. (d693cd2)
  2. Ambient admitting committed outputs (useAmbientSubgraphPreviews.ts:59) — gate narrowed to nodePreviewImages only; nodeOutputStore.ts now has zero diff vs main. (f604b85)
  3. Media-type derivation (useAmbientSubgraphPreviews.ts:71) — confirmed moot per Configure vite to copy from src to dist #2: ambient now only ever carries b_preview_with_metadata blob frames, which are always images. No code change.
  4. Guard test coverage (useAmbientSubgraphPreviews.test.ts:100) — added mockReturnValue stubs so the isDetached/nested-SubgraphNode tests are mutation-killing, added a hideOutputImages test, deduped the byte-identical test, and switched the hardcoded type: 'image' assertion to getPreviewMediaType. (a356ced)
  5. Docblock accuracy (useAmbientSubgraphPreviews.ts:20) — corrected "collapsed" → "expanded" host and softened the nodeLocationProgressStates analogy to note the depth-1 limitation. (f604b85)
  6. Extract pure merge function (LGraphNode.vue:725) — mergeSubgraphPreviews(promoted, ambient, exposedSourceNodeIds) in src/composables/node/mergeSubgraphPreviews.ts, with unit tests. (d693cd2)
  7. Breadcrumb comments (LGraphNode.test.ts:399, imagePreview.spec.ts:192) — restored the incident-94 / LGraphNode.vue:172 pointer on both markers. (294e3fb)
  8. CI wiring (subgraphConcurrentPreviews.spec.ts:22) — confirmed via playwright test --list --project=chromium that the spec is discovered under the same project/tag filter CI runs; no config change needed.

pnpm typecheck, pnpm lint, and the full touched-file vitest suite are green. Pushed as 4 new commits on top of the existing branch history (no rebase/force-push): f604b85, d693cd2, a356ced, 294e3fb.

Also saw your scope note on classic-canvas parity and the Fixes/Refs correction — nothing further needed there, just acknowledging.


Generated by Claude Code

@christian-byrne

christian-byrne commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Status for whoever picks this up next.

@DrJKL adding you as co-assignee since you own this area (src/core/graph/subgraph/promotionUtils.ts, src/stores/previewExposureStore.ts, and you authored the guard in #12197). All 8 review findings are fixed and CI is green; the PR is conflict-free against main despite being ~212 commits behind. It is blocked only on a human approval — the existing review is COMMENTED, not APPROVED.

Two things worth deciding before this merges, both of which outlive this PR:

Bug 2a is still unowned. The !lgraphNode?.isSubgraphNode() guard at LGraphNode.vue:172 is unchanged on main. This PR carries two .fail()-marked tests documenting it (per your call on 2026-08-02) but does not fix it. It is also not on the incident's bug list, so it is invisible to anyone reading the tracker. Worth noting that app.ts:861 already fans the preview blob to every ancestor execution id including the host's own locator, so deleting that one condition may be the whole fix, renderer-agnostic and depth-agnostic. Happy to red-green it as a separate PR if you want it.

Classic canvas was scoped out on 2026-08-04 and never revisited. getPseudoWidgetPreviewTargets is exposure-only, so LiteGraph-mode users still have the original bug. That may be fine given the Nodes 2.0 rollout momentum, but it should be an explicit call rather than a silent gap.

Also worth linking, both unfiled against this incident: https://linear.app/comfyorg/issue/FE-616 ("Multiple subgraphs on one canvas get same previews from each other", closed as Duplicate with no target) is the same definition-scoped locator bleed that still exists in nodeOutputStore today, and https://linear.app/comfyorg/issue/FE-1529 is a fresh report in the same area.

Comment thread browser_tests/tests/subgraph/subgraphConcurrentPreviews.spec.ts Outdated
@claude

claude Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Per Christian's Slack ask, expanded e2e (+1 unit) coverage for pathways beyond the two original specs. Summary of what I audited and added:

New e2e specs (browser_tests/tests/subgraph/):

  • subgraphNestedAmbientPreviewScope.spec.ts — a live sampler two subgraph levels deep only bubbles to its immediate host, not the outermost one, matching useAmbientSubgraphPreviews's explicit "nested SubgraphNodes derive their own previews independently" behavior. Verifies zero images on the outer host, then one on the middle host after entering. New fixture: subgraph-nested-with-live-sampler.json.
  • subgraphAmbientCommittedOutputIsolation.spec.ts — a committed (executed) output from an unexposed PreviewImage node never leaks into the ambient rollup (only nodePreviewImages-gated streaming frames should), and the live sampler in the same subgraph still shows correctly afterward. This is the exposure-leak bug class end-to-end (WS → store → render), on top of the existing unit-only regression test. New fixture: subgraph-with-live-sampler-and-committed-output.json.
  • Extended subgraphConcurrentPreviews.spec.ts with a case for mergeSubgraphPreviews's actual purpose: un-promoting a still-live sampler's $$canvas-image-preview via the SubgraphEditor doesn't blank it — the ambient rollup immediately takes back over since exclusion is keyed off the host's current exposure set, not baked in at merge time.

Unit: useAmbientSubgraphPreviews.test.ts gained a video/audio previewMediaType case (mirrors the one usePromotedPreviews.test.ts already had) — the ambient path shared getPreviewMediaType but only had image-type coverage.

Audited, not covered (flagging rather than guessing):

  • Multiple sibling instances of the same subgraph definition, each with its own live ambient preview. Tracing useAmbientSubgraphPreviewsnodeOutputStore.getNodeImageUrls/setNodePreviewsByExecutionIdexecutionIdToNodeLocatorId, every instance's per-node preview resolves to the same definition-scoped NodeLocatorId (types/nodeIdentification.ts says this is intentional: "remains the same for all instances"). That would mean two sibling instances of one subgraph type showing the same live image cross-instance — but this looks like a pre-existing property of the whole preview/output store, not something introduced by this PR, and I can't execute against a live backend here to confirm real-world behavior. Flagging for your awareness rather than asserting untested behavior; happy to dig in further if useful.
  • Video-media-type ambient preview at e2e level, and collapsed-subgraph-node interaction: lower value/higher setup cost for what they'd add over the unit-level and generic-collapse coverage that already exists.
  • Didn't touch the two !lgraphNode?.isSubgraphNode()-guard expected-fail pins (incident-94, tracked separately).

pnpm typecheck, typecheck:browser, oxlint, eslint clean; new/changed unit tests pass. New e2e specs weren't runnable in my sandbox (no live backend) — verified by close reading against the actual composable/store code and by matching existing spec conventions; please confirm in CI.

84476ce

@github-actions github-actions Bot added the risk:R2 PR risk grade (advisory shadow check; grader-owned) label Aug 17, 2026
Comment thread src/renderer/extensions/vueNodes/components/LGraphNode.vue
Comment thread src/composables/node/useAmbientSubgraphPreviews.ts Outdated
Comment thread src/composables/node/useAmbientSubgraphPreviews.test.ts
Comment thread src/composables/node/useAmbientSubgraphPreviews.ts
Comment thread src/renderer/extensions/vueNodes/components/LGraphNode.vue Outdated
@christian-byrne christian-byrne self-assigned this Aug 23, 2026
christian-byrne added a commit that referenced this pull request Aug 23, 2026
Adds e2e coverage for `Convert to Subgraph` with a **partial**
selection.

## The gap

`browser_tests/tests/subgraph/subgraphCrud.spec.ts:68` is the only e2e
test of subgraph creation. It calls `comfyPage.keyboard.selectAll()`
first, so every node is inside the selection, **there are zero boundary
links**, and the entire rewiring block in `_convertToSubgraphImpl`
(`src/lib/litegraph/src/LGraph.ts:1861-1939`) never executes. It then
asserts node counts, never link topology.

Partial selection — what users actually do — had no e2e coverage at all.
That block reconnects boundary links by positional index
(`subgraphNode.inputs[i - 1]`, `subgraphNode.outputs[i - 1]`) against a
slot array built independently in `mapSubgraphInputsAndLinks`, so a
drift there is silent.

## What it does

Packs `KSampler` + `VAE Decode` out of the default workflow and asserts:

- the exact post-conversion link table, with the new subgraph node
rendered as `HOST` (five boundary inputs rewired onto it, one boundary
output to SaveImage, and the two `CheckpointLoaderSimple → CLIP` links
that lie entirely outside the selection left untouched)
- every rewired link lands on a type-compatible slot
- the result survives a serialize/reload round trip

## Status — currently red, cause not yet established

Please do not read the red as a confirmed product bug yet; I do not want
to overstate it.

The first run failed, but **my expected link count was wrong** (I
asserted six root links and forgot the two external-only CLIP links; the
correct total is eight). That error is fixed in `0566c82c5a`, which now
asserts the full topology rather than a count.

What the first run reported that is *not* explained by my arithmetic
error, and is still open:

- the `4:2` VAE boundary link was absent from the links targeting the
host
- one host input slot was left unconnected
- SaveImage's input was not fed by the host
- nine root links, where the corrected expectation is eight

Against that, a faithful unit-level reduction of the same graph shape
(same node/slot/type layout, same selection) **passes**: all five host
inputs connect, SaveImage is fed by the host, and the link count is
eight. So either the trigger is something the reduction lacks (real node
defs, widget-backed inputs, the app's `SubgraphNode` registration path),
or the browser selection was not the two nodes I assumed. The corrected
assertion prints the actual table on failure, which should settle it on
the next run.

I have deliberately **not** written a fix. Test-only PR.

## Related

- Does **not** overlap
#15002 (subgraph
**unpack**, FE-1539 — different action, different code path
`_unpackSubgraphImpl`).
- Does **not** overlap
#15009 /
#15018, which are
based on `feature/ecs-migration` and change `src/stores/linkStore.ts` /
`rerouteStore.ts` — files that do not exist on `main`.
- Does **not** overlap
#14569 (preview/output
display only).

---------

Co-authored-by: Connor Byrne <c.byrne@comfy.org>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: DrJKL <DrJKL0424@gmail.com>
Co-authored-by: Amp <amp@ampcode.com>
claude and others added 13 commits August 23, 2026 08:06
useAmbientSubgraphPreviews treated any interior node with images in
nodeOutputStore as a live preview candidate, including nodes whose only
"output" is an input-type reference (e.g. an unpromoted LoadImage node's
own selected file). That made every such node's thumbnail always visible
on the collapsed subgraph host regardless of promotion state, breaking
promotion exclusivity.

CI caught this: browser_tests/tests/subgraph/subgraphPromotion.spec.ts
"Can promote multiple previews" > "Promote both image previews" failed
consistently (all 4 attempts) on PR #14569 - after promoting only one of
two Load Image previews, the host showed 2 preview elements instead of 1,
because the unpromoted node's own file leaked in via the new ambient
rollup.

Export isInputPreviewOutput from nodeOutputStore and use it to skip
input-only outputs in the ambient computed, so only genuine live
execution results (or streaming preview frames) surface ambiently -
matching the feature's stated intent (a second concurrently-executing
node with no exposure), not static input previews.

Claude-Session: https://claude.ai/code/session_01FCL4Meqh8o8VYh9xV1h1Ky
#14551 was closed in favor of this PR's ambient-rollup mechanism, but it
carried three artifacts not otherwise covered here:

- browser_tests/assets/nodes/sampler_custom_advanced_in_subgraph.json +
  browser_tests/tests/subgraph/subgraphPreviewAllowlistGap.spec.ts: this
  PR's own e2e fixture uses two KSamplers, both allowlisted, so it would
  pass even if a node-type gate were reintroduced into the ambient path.
  This fixture pins a non-allowlisted node type (SamplerCustomAdvanced)
  instead.
- Two incident-94 expected-fail regression tests (LGraphNode.test.ts,
  imagePreview.spec.ts) pinning the still-open `!lgraphNode?.isSubgraphNode()`
  guard at LGraphNode.vue:172, which neither PR touches.
Remove comments that restate what the code does or reference the
incident/PR that motivated the change. Keep only comments that capture
a genuine non-obvious invariant, rewritten to describe the invariant
itself instead of pointing at an incident or ticket number.
Admitting committed nodeOutputs alongside nodePreviewImages made
demoting an exposure a permanent no-op (the ambient rollup kept
re-rendering what removeExposure just deleted, and queueStore
repopulates nodeOutputs on history load so it survived reload), stacked
a preview on the host for every interior output node after every run,
and fed committed-output nodes into buildImageUrls, which appends a
fresh &rand= per call and re-fetches at latent-preview frame rate.

Narrow the gate to nodePreviewImages only. isInputPreviewOutput was
exported from nodeOutputStore solely for the dropped branch and has no
other external callers, so drop the export too - nodeOutputStore.ts's
diff against main is now empty.

Also correct two docblock claims: previews render when the host is
expanded (v-else-if="!isCollapsed" in LGraphNode.vue), not collapsed,
and this composable bails at depth 1 instead of recursing like
nodeLocationProgressStates.
exposedNodeIds was built from promotedPreviews[].sourceNodeId, which is
the chain-resolved leaf id for exposures that come from a nested
subgraph. useAmbientSubgraphPreviews emits immediate-interior ids
instead. Since last_node_id is per-LGraph, ids restart at 1 in every
subgraph, so a nested exposure resolving to leaf id N could collide
with and suppress the host's own interior node N.

Build the exclusion set from previewExposureStore.getExposures(
rootGraphId, hostLocator) instead - those are, by schema, direct
children of the host's own subgraph, so both sides of the dedup are in
the same id space.

Also extract the promoted/ambient merge into a pure
mergeSubgraphPreviews function with unit tests covering no overlap, an
overlapping exposure winning over its ambient duplicate, and the
namespace-collision regression above.
getNodeImageUrls is an unstubbed vi.fn(), reset to undefined by
vi.resetAllMocks(), so `if (!urls?.length) return []` short-circuited
before the isDetached bail, the nested-SubgraphNode skip, or the
hideOutputImages guard ever ran - deleting any of the three still left
every test green. Add mockReturnValue stubs so the detached and
nested-SubgraphNode tests actually reach their guard, and add a test
for the previously uncovered hideOutputImages guard.

Also dedupe the byte-identical "no output" test by differentiating it
into a test for the getNodeImageUrls-returns-nothing case, add a
regression test locking in the nodeOutputs exclusion from the previous
commit, and assert media type via getPreviewMediaType instead of a
hardcoded 'image' literal so a change to that derivation isn't invisible
to the suite.
A prior comment-cleanup pass reworded these it.fails/test.fail markers
to drop the incident name and exact guard line. Without that pointer,
whoever eventually fixes LGraphNode.vue:172's
!lgraphNode?.isSubgraphNode() guard hits a bare "Error: Expect test to
fail" with no context. Add the breadcrumb back.
Adds e2e coverage for pathways the two existing preview specs don't
reach: an ambient preview nested two subgraph levels deep bubbles to
its immediate host only, not the outermost one; a committed
(non-live) interior output never gets conflated with the ambient
rollup; and un-promoting a still-live sampler's exposed preview falls
back to its ambient preview instead of disappearing. Also fills a
small unit gap: useAmbientSubgraphPreviews derives video/audio media
types the same way the promoted path already does.
removeExposure had no tombstone, so useAmbientSubgraphPreviews would
re-surface a preview the next frame after a user explicitly demoted it,
as long as the interior node kept producing live output.

Add a suppressedAmbientNodeIds tombstone set to previewExposureStore,
keyed per (rootGraphId, hostNodeLocator): removeExposure adds the
removed source node to it, addExposure clears it, and clearGraph drops
it with the rest of the host's state. useAmbientSubgraphPreviews now
excludes suppressed node ids from its interior-node scan.

Also documents two invariants raised in review: nodePreviewImages is
only ever written by live execution/streaming frames, never static
input data, so the single-branch gate needs no isInputPreviewOutput
guard; and subgraph.nodes is a plain non-reactive array, so structural
add/remove while collapsed isn't tracked until the next preview frame.
… own regression test

b6bc646 added a suppressedAmbientNodeIds tombstone so removeExposure
would permanently hide an interior node's ambient preview. That
directly reverses this PR's stated design ("any interior node
currently producing output shows on the host, whether or not anyone
promoted it") and broke the e2e regression test added earlier in this
branch (subgraphConcurrentPreviews.spec.ts: "un-promoting a still-live
sampler falls back to its ambient preview instead of disappearing"),
which was failing in CI.

Un-promoting a node only removes its explicit exposure (name/ordering
in the promoted slot); it was never meant to blacklist the node from
the ambient rollup while it keeps producing live output. Revert the
tombstone mechanism and its tests, restoring the pre-b6bc646 behavior
that the existing regression test expects.
@claude
claude Bot force-pushed the fix/decouple-subgraph-preview-outputs branch from 4613373 to e36ee9d Compare August 23, 2026 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:R2 PR risk grade (advisory shadow check; grader-owned) size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants