Skip to content

test: custom-node E2E regression suite#13389

Open
CodeJuggernaut wants to merge 66 commits into
mainfrom
nathaniel/custom-node-e2e-suite
Open

test: custom-node E2E regression suite#13389
CodeJuggernaut wants to merge 66 commits into
mainfrom
nathaniel/custom-node-e2e-suite

Conversation

@CodeJuggernaut

@CodeJuggernaut CodeJuggernaut commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Playwright suite proving community custom-node packs register, render under both renderers (LiteGraph canvas and Vue Nodes 2.0), wire type-correct connections, and execute real workflows, with every browser test asserting the app shows zero visible errors. Manifest-driven: 7 packs covered (Impact, VideoHelperSuite, rgthree, essentials, KJNodes, Custom-Scripts, WAS), and adding another is one JSON row plus one small workflow file. A gating CI job installs every manifest pack from scratch (pinned to verified commits) and fails on any test failure, any pack-install error, or any skipped test.

Beyond the curated per-pack tiers, the suite covers EVERY node the packs register - 823 nodes total, discovered live from the backend with zero configuration: each mounts in both renderers (the Vue mount also asserts the DOM renders the instance's widget and slot counts), survives save/reload twice (a pristine pass where reload must never shrink a node or change a value - the "widgets disappear after reload" bug class - and a set-and-stick pass where every plain widget must hold a programmatic non-default write), has its concrete slots wired through the typed-connection sweep (5,058 pairs; COMBO slots pair when their option vocabularies match exactly), and executes for real on the backend (446 nodes run clean): self-sufficient nodes run on their widget defaults, and nodes whose required sockets have model-free producers (EmptyImage, EmptyLatentImage, SolidMask, Primitive*, EmptyAudio) get those producers synthesized and wired automatically. Executed nodes must observably produce - the PreviewAny sink wired to each node must emit a ui payload. Nodes that cannot run are classified and reconciled against a committed per-pack baseline that is asserted both ways, so it can neither hide regressions nor rot. Every escape hatch is a reviewed ledger entry carrying its mechanism (details in ADDING_CUSTOM_NODES.md).

The all-nodes sweep also surfaced real bugs, worked around in the harness and queued for upstream reports: the frontend widgetValueStore keeps widget state keyed by node id across graph.clear(), so a recreated node that reuses an id inherits a deleted node's same-named widget values (the suite never reuses ids); WAS Text Find and Replace Input infinite-loops the server on an empty find string; WAS Image Rembg runs pip install inside execute; VHS per_batch serializes to null after reload; KJNodes ImageGridtoBatch ships a default that violates its own minimum.

Changes

  • What: browser_tests/tests/customNodes/ suite + browser_tests/fixtures/customNode/ harness (in-page event tap, pure run classifier, object_info validator, manifest loader, type-pairing generator). Tiers per pack: T0 load/render (per-node data-node-id assertion under Vue Nodes), connectivity (one representative typed edge per slot planned from /object_info, connected through the real isValidConnection veto, survives serialize/configure/graphToPrompt, plus curated real slot drags under both renderers), T1 run (per-node execution verified via the executing event stream). Zero-visible-errors invariant across error overlay/dialog/node errors/toasts, with a self-check that forces an execution error and asserts the overlay appears (positive control).
  • Optional vueNodesCompatible manifest field: a pack proven unable to mount under Vue Nodes 2.0 runs its LiteGraph assertions only. Never a test.skip, so the zero-skip CI gate stays honest. No current pack needs it (all 7 mount under Vue Nodes empirically); the mechanism is unit-tested and its evidence rule is documented.
  • .github/workflows/ci-tests-custom-nodes.yaml: gating job that clones and pip-installs every manifest pack (CPU torch constraints), boots the backend, runs the suite, and forbids skipped tests.
  • browser_tests/tests/customNodes/ADDING_CUSTOM_NODES.md: the authoritative step-by-step process for onboarding a new pack (verify real node keys, author the workflow, manifest field reference, local verify, CI expectations), followable by humans and agents.
  • ComfyPage.createUser is now idempotent on 400 Duplicate username. so the fixture works against single-user server-storage backends.
  • playwright.chrome.config.ts: opt-in system-Chrome config (trace off; the trace recorder crashes pages under the branded Chrome channel).

New Scripts Added

High level (full command table with every variant lives in the committed suite README, browser_tests/tests/customNodes/README.md):

  • Run everything: pnpm test:custom-nodes runs the whole suite headless. This is the pass/fail gate: every tier for every pack, zero skips.
  • Run one pack or one tier: targeted variants run a single pack's render or execution tier, the connectivity tier alone, or the error-detection self-check, so you can iterate on one pack without paying for the full suite.
  • Watch and interactive debug: a headed slow-motion watch mode, and a debug mode that opens the Playwright Inspector so you can step through robot actions one at a time (F10 step, F8 resume). Any -g pattern works against the generic scripts for ad-hoc filtering.

Prerequisites (backend flags, dev server) and a worked example are in the same README; the process for adding new packs and their scripts is in ADDING_CUSTOM_NODES.md.

Review Focus

  • The executed-set contract in runResult.ts: only non-null executing events count as executed (cache-safe); the run tier therefore requires a --cache-none backend, documented in the README and manifest field docs.
  • The CI job is intended to become a required status check (custom-nodes-e2e). Path gating lives in a changes job, not trigger-level paths:, so the required check never wedges Pending on unrelated PRs. Fork PRs skip it (the manifest install loop is a code-execution surface) and keep coverage via the main e2e shards.
  • Shared-fixture edit in ComfyPage.createUser (blast radius on existing suites is nil: the 200 path is unchanged).
  • Shared-fixture edit in litegraphUtils.ts NodeSlotReference.getPosition: slot positions now add the canvas element's page offset so mouse targeting survives pack JS that injects page chrome (rgthree's progress bar shifts the canvas 16px). Exact no-op when the canvas sits at (0,0), which is every stock environment - verified by the full suite passing against both the dev server and a dist-serving backend.

Local verification: 67 passed / 0 skipped / 0 failed against ComfyUI v0.26.2 (CPU, --multi-user --cache-none) with all 7 manifest packs installed at their pinned commits; lint, format, knip, and both typechecks clean under the CI-mirror gate.

Data-driven Playwright harness verifying custom-node packs load and render
under both LiteGraph 1.0 and Vue Nodes 2.0 against a real ComfyUI backend.
Pure classifier/validator/manifest logic is unit-tested; the regression spec
renders each pack's nodes in both renderers (Vue via data-node-id DOM) and a
self-check runs a workflow to confirm execution-error capture. Proven against
Impact Pack + VideoHelperSuite.

Makes ComfyPage.createUser idempotent so the suite runs against a persistent
backend (Desktop server user storage).
Normalize the executing event tap: its CustomEvent detail is a bare node-id
string, so the previous object-spread left the executed-set permanently empty.
The self-check now asserts a non-empty executed-set to keep that path live.

T0 now clears the graph per renderer pass, asserts exact node counts, and
verifies each added pack node's own data-node-id mounts in the Vue pass
(default-workflow nodes can no longer satisfy the assertions). Console capture
starts before the renderer toggle. Added an object_info sanity floor so a
depleted getNodeDefs fails loudly instead of skipping everything.

Run/io tiers gain test.setTimeout, requiresModels gating, and an empty-workflow
guard. Interrupted runs get pure-spec coverage; the shared console collector
moves to fixtures/utils.
Impact runs ImpactInt and ImpactFloat into PreviewAny as a group; VHS decodes
the existing plain_video.mp4 asset through VHS_LoadVideoPath into
VHS_VideoInfo. The executed-set check asserts each expected node individually
executed, so group workflows still verify per-node execution. Requires a
cache-disabled backend (--cache-none) with the video staged in its input dir;
documented on the manifest workflow field.
Every browser tier now asserts the app's user-facing error surfaces (error
overlay, error dialog, node render errors, error toasts) are absent at test
start and after each pass, so a run is green only if a human watching the
screen sees zero errors. The harness self-check asserts the overlay IS
visible after a forced execution error, keeping the selectors provably live.

Sessions boot with a blank graph (Comfy.TutorialCompleted=false) because the
bundled default template references models absent on a scoped backend; the
tutorial path's auto-opened template browser is dismissed per test. Settings
now reach the session on single-user server-storage backends by routing
devtools set_settings to the default user, and the errors tab stays enabled
so error indicators are never suppressed in this suite. The smoke test loads
a core-only model-free workflow instead of the SD1.5 default asset.
T2a gated on the ComfyUI-test-framework 'Assert Executed' nodes, which are
not published for any backend yet, so the tier could only ever skip. A test
that cannot run anywhere is reporting noise; restore it from history when
the assertion pack lands. Suite is now 16 passed, zero skips, zero failures.
test:custom-nodes runs the whole suite headless (the gate); :watch opens a
headed slow-motion run of the browser tiers; :debug steps through them in the
Playwright Inspector. All target the local dev server on :5173 and use the
committed system-Chrome config (no bundled-chromium download). Pass -g to
:watch / :debug to run a single test, e.g. -g 'VideoHelperSuite.*T1'.
…uite

One script per pack tier (impact-render/impact-run/vhs-render/vhs-run) plus
the self-check, all opening the Playwright Inspector so anyone can step
through what the robot does. README covers prerequisites, every script, a
worked example, the zero-visible-errors contract, and how to add a pack.
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🌐 Website E2E

Tip

All tests passed.

Status ✅ Passed
Report View Report

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 07/09/2026, 02:18:40 AM UTC

Links

🎭 Playwright: ✅ 1708 passed, 0 failed · 2 flaky

📊 Browser Reports
  • chromium: View Report (✅ 1687 / ❌ 0 / ⚠️ 2 / ⏭️ 40)
  • 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: 7.78 MB gzip 🟢 -30 B

Details

Summary

  • Raw size: 32.8 MB baseline 32.8 MB — ⚪ 0 B
  • Gzip: 7.78 MB baseline 7.78 MB — 🟢 -30 B
  • Brotli: 5.35 MB baseline 5.35 MB — 🟢 -39 B
  • Bundles: 300 current • 300 baseline • 134 added / 134 removed

Category Glance
Vendor & Third-Party ⚪ 0 B (15.3 MB) · Other ⚪ 0 B (11.7 MB) · Utilities & Hooks ⚪ 0 B (3.39 MB) · Graph Workspace ⚪ 0 B (1.27 MB) · Panels & Settings ⚪ 0 B (547 kB) · Data & Services ⚪ 0 B (270 kB) · + 5 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-Dl0fBrnu.js (new) 47.8 kB 🔴 +47.8 kB 🔴 +14.1 kB 🔴 +12.2 kB
assets/index-ouUz4R_1.js (removed) 47.8 kB 🟢 -47.8 kB 🟢 -14.1 kB 🟢 -12.2 kB

Status: 1 added / 1 removed

Graph Workspace — 1.27 MB (baseline 1.27 MB) • ⚪ 0 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-BC4r9Fl4.js (new) 1.27 MB 🔴 +1.27 MB 🔴 +271 kB 🔴 +203 kB
assets/GraphView-BSPykVLb.js (removed) 1.27 MB 🟢 -1.27 MB 🟢 -271 kB 🟢 -203 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-B9Lcplq_.js (new) 19.4 kB 🔴 +19.4 kB 🔴 +5.04 kB 🔴 +4.46 kB
assets/CloudSurveyView-CWomKDwt.js (removed) 19.4 kB 🟢 -19.4 kB 🟢 -5.04 kB 🟢 -4.47 kB
assets/CloudLoginView-a2vVOIX7.js (removed) 11.4 kB 🟢 -11.4 kB 🟢 -3.07 kB 🟢 -2.68 kB
assets/CloudLoginView-DY-cAylW.js (new) 11.4 kB 🔴 +11.4 kB 🔴 +3.07 kB 🔴 +2.69 kB
assets/CloudSignupView-COG3V7gB.js (new) 9.79 kB 🔴 +9.79 kB 🔴 +2.74 kB 🔴 +2.41 kB
assets/CloudSignupView-DkSqZVP0.js (removed) 9.79 kB 🟢 -9.79 kB 🟢 -2.74 kB 🟢 -2.4 kB
assets/CloudLayoutView-BlmCYOQB.js (new) 9.36 kB 🔴 +9.36 kB 🔴 +2.34 kB 🔴 +2.03 kB
assets/CloudLayoutView-Y_m8qWab.js (removed) 9.36 kB 🟢 -9.36 kB 🟢 -2.34 kB 🟢 -2.03 kB
assets/UserCheckView-DKQZ6F5I.js (removed) 8.8 kB 🟢 -8.8 kB 🟢 -2.22 kB 🟢 -1.93 kB
assets/UserCheckView-UUSPK7YT.js (new) 8.8 kB 🔴 +8.8 kB 🔴 +2.22 kB 🔴 +1.94 kB
assets/CloudSubscriptionRedirectView-CJJsC5u3.js (new) 6.98 kB 🔴 +6.98 kB 🔴 +2.58 kB 🔴 +2.28 kB
assets/CloudSubscriptionRedirectView-CRUuoYzW.js (removed) 6.98 kB 🟢 -6.98 kB 🟢 -2.58 kB 🟢 -2.27 kB
assets/UserSelectView-BGICtFeq.js (removed) 6 kB 🟢 -6 kB 🟢 -2.15 kB 🟢 -1.89 kB
assets/UserSelectView-eptimd9F.js (new) 6 kB 🔴 +6 kB 🔴 +2.15 kB 🔴 +1.89 kB
assets/CloudForgotPasswordView-B7yT2QOT.js (new) 5.15 kB 🔴 +5.15 kB 🔴 +1.76 kB 🔴 +1.54 kB
assets/CloudForgotPasswordView-CZ1_HdPT.js (removed) 5.15 kB 🟢 -5.15 kB 🟢 -1.76 kB 🟢 -1.54 kB
assets/CloudAuthTimeoutView-B4cPxSAH.js (removed) 4.49 kB 🟢 -4.49 kB 🟢 -1.58 kB 🟢 -1.37 kB
assets/CloudAuthTimeoutView-rkaLJfO8.js (new) 4.49 kB 🔴 +4.49 kB 🔴 +1.57 kB 🔴 +1.37 kB

Status: 9 added / 9 removed / 3 unchanged

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-D1OKrB4a.js (new) 49.4 kB 🔴 +49.4 kB 🔴 +9.97 kB 🔴 +8.82 kB
assets/KeybindingPanel-DJ74XopU.js (removed) 49.4 kB 🟢 -49.4 kB 🟢 -9.97 kB 🟢 -8.82 kB
assets/SecretsPanel-eX1N0OJm.js (new) 25.1 kB 🔴 +25.1 kB 🔴 +5.93 kB 🔴 +5.21 kB
assets/SecretsPanel-ku9GKy22.js (removed) 25.1 kB 🟢 -25.1 kB 🟢 -5.93 kB 🟢 -5.22 kB
assets/CreditsPanel-BnXbo6Fy.js (new) 15.9 kB 🔴 +15.9 kB 🔴 +4.66 kB 🔴 +4.09 kB
assets/CreditsPanel-F_Er-xuX.js (removed) 15.9 kB 🟢 -15.9 kB 🟢 -4.66 kB 🟢 -4.09 kB
assets/AboutPanel-BsCvZyJz.js (removed) 12 kB 🟢 -12 kB 🟢 -3.29 kB 🟢 -2.96 kB
assets/AboutPanel-WlnANmL4.js (new) 12 kB 🔴 +12 kB 🔴 +3.29 kB 🔴 +2.95 kB
assets/SubscriptionPanel-DeZBhVk6.js (removed) 11.4 kB 🟢 -11.4 kB 🟢 -3.64 kB 🟢 -3.2 kB
assets/SubscriptionPanel-XZFoMHlZ.js (new) 11.4 kB 🔴 +11.4 kB 🔴 +3.63 kB 🔴 +3.2 kB
assets/ExtensionPanel-B06lG16z.js (new) 9.03 kB 🔴 +9.03 kB 🔴 +2.5 kB 🔴 +2.2 kB
assets/ExtensionPanel-D6VLXgv6.js (removed) 9.03 kB 🟢 -9.03 kB 🟢 -2.5 kB 🟢 -2.2 kB
assets/ServerConfigPanel-BuhbJMEh.js (removed) 6.15 kB 🟢 -6.15 kB 🟢 -1.97 kB 🟢 -1.75 kB
assets/ServerConfigPanel-CrI9R_rz.js (new) 6.15 kB 🔴 +6.15 kB 🔴 +1.97 kB 🔴 +1.76 kB
assets/UserPanel-DTqaRWsQ.js (removed) 5.78 kB 🟢 -5.78 kB 🟢 -1.82 kB 🟢 -1.58 kB
assets/UserPanel-nWFetrl0.js (new) 5.78 kB 🔴 +5.78 kB 🔴 +1.82 kB 🔴 +1.57 kB
assets/refreshRemoteConfig-BPs0ztLU.js (new) 2.88 kB 🔴 +2.88 kB 🔴 +1.26 kB 🔴 +1.11 kB
assets/refreshRemoteConfig-DF4oGapq.js (removed) 2.88 kB 🟢 -2.88 kB 🟢 -1.26 kB 🟢 -1.11 kB
assets/cloudRemoteConfig-CzgX6Lyi.js (removed) 990 B 🟢 -990 B 🟢 -542 B 🟢 -455 B
assets/cloudRemoteConfig-wB8JQwy-.js (new) 990 B 🔴 +990 B 🔴 +543 B 🔴 +462 B
assets/refreshRemoteConfig-5quRM6KT.js (removed) 110 B 🟢 -110 B 🟢 -89 B 🟢 -81 B
assets/refreshRemoteConfig-DkzXPtaw.js (new) 110 B 🔴 +110 B 🔴 +89 B 🔴 +81 B

Status: 11 added / 11 removed / 16 unchanged

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SignUpForm-C8XhKdqT.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -4.19 kB 🟢 -3.66 kB
assets/SignUpForm-UPhgS1fo.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +4.19 kB 🔴 +3.66 kB
assets/auth-BFLgQ28g.js (new) 3.69 kB 🔴 +3.69 kB 🔴 +1.3 kB 🔴 +1.13 kB
assets/auth-Dz1ozVfU.js (removed) 3.69 kB 🟢 -3.69 kB 🟢 -1.3 kB 🟢 -1.11 kB
assets/usePostAuthRedirect-C8iJvUaF.js (new) 3.33 kB 🔴 +3.33 kB 🔴 +1.27 kB 🔴 +1.11 kB
assets/usePostAuthRedirect-oFLbYjEy.js (removed) 3.33 kB 🟢 -3.33 kB 🟢 -1.27 kB 🟢 -1.11 kB
assets/UpdatePasswordContent-BmxUPnsE.js (removed) 1.92 kB 🟢 -1.92 kB 🟢 -876 B 🟢 -763 B
assets/UpdatePasswordContent-BtuIeEVs.js (new) 1.92 kB 🔴 +1.92 kB 🔴 +877 B 🔴 +768 B
assets/authStore-B4zqOTHM.js (new) 130 B 🔴 +130 B 🔴 +109 B 🔴 +124 B
assets/authStore-fc2oQ3Ii.js (removed) 130 B 🟢 -130 B 🟢 -109 B 🟢 -107 B
assets/workspaceAuthStore-DiKS5M0Q.js (removed) 110 B 🟢 -110 B 🟢 -104 B 🟢 -114 B
assets/workspaceAuthStore-DO05Y_-S.js (new) 110 B 🔴 +110 B 🔴 +104 B 🔴 +113 B
assets/auth-CkqI3qws.js (removed) 105 B 🟢 -105 B 🟢 -96 B 🟢 -79 B
assets/auth-DvLmv4aI.js (new) 105 B 🔴 +105 B 🔴 +96 B 🔴 +94 B

Status: 7 added / 7 removed / 3 unchanged

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

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyHubPublishDialog-CH-P9JOv.js (removed) 90.5 kB 🟢 -90.5 kB 🟢 -19.3 kB 🟢 -16.5 kB
assets/ComfyHubPublishDialog-lPKpuD4c.js (new) 90.5 kB 🔴 +90.5 kB 🔴 +19.3 kB 🔴 +16.5 kB
assets/useShareDialog-DoTaEv2A.js (new) 23.7 kB 🔴 +23.7 kB 🔴 +5.59 kB 🔴 +4.97 kB
assets/useShareDialog-NCxbhQNl.js (removed) 23.7 kB 🟢 -23.7 kB 🟢 -5.59 kB 🟢 -4.96 kB
assets/ComfyHubPublishDialog-DDTJRBFv.js (new) 143 B 🔴 +143 B 🔴 +105 B 🔴 +88 B
assets/ComfyHubPublishDialog-YXh7JE8C.js (removed) 143 B 🟢 -143 B 🟢 -105 B 🟢 -89 B
assets/useSubscriptionDialog-B8QdtTyu.js (removed) 110 B 🟢 -110 B 🟢 -102 B 🟢 -91 B
assets/useSubscriptionDialog-CzlYuEkd.js (new) 110 B 🔴 +110 B 🔴 +102 B 🔴 +98 B

Status: 4 added / 4 removed / 1 unchanged

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-AeFulHOQ.js (new) 13.6 kB 🔴 +13.6 kB 🔴 +3.82 kB 🔴 +3.41 kB
assets/ComfyQueueButton-Cj1UgLr0.js (removed) 13.6 kB 🟢 -13.6 kB 🟢 -3.82 kB 🟢 -3.41 kB
assets/useTerminalTabs-C4zP1QcI.js (removed) 12.1 kB 🟢 -12.1 kB 🟢 -3.84 kB 🟢 -3.38 kB
assets/useTerminalTabs-Cb7Y4Z3J.js (new) 12.1 kB 🔴 +12.1 kB 🔴 +3.84 kB 🔴 +3.39 kB
assets/SubscribeButton-C_-UlLcR.js (removed) 2.38 kB 🟢 -2.38 kB 🟢 -1.05 kB 🟢 -917 B
assets/SubscribeButton-WJwV1h-6.js (new) 2.38 kB 🔴 +2.38 kB 🔴 +1.05 kB 🔴 +919 B
assets/cloudFeedbackTopbarButton-CueRpzcY.js (removed) 829 B 🟢 -829 B 🟢 -499 B 🟢 -450 B
assets/cloudFeedbackTopbarButton-zQzcHcCc.js (new) 829 B 🔴 +829 B 🔴 +498 B 🔴 +422 B
assets/ComfyQueueButton-Dsnnb3ft.js (new) 128 B 🔴 +128 B 🔴 +99 B 🔴 +93 B
assets/ComfyQueueButton-DVyr9pHJ.js (removed) 128 B 🟢 -128 B 🟢 -99 B 🟢 -89 B

Status: 5 added / 5 removed / 8 unchanged

Data & Services — 270 kB (baseline 270 kB) • ⚪ 0 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/load3dService-Bx0dpoy6.js (removed) 126 kB 🟢 -126 kB 🟢 -27.8 kB 🟢 -23.5 kB
assets/load3dService-ZP_Ei6mT.js (new) 126 kB 🔴 +126 kB 🔴 +27.8 kB 🔴 +23.5 kB
assets/api-DY_5xs7U.js (removed) 91.9 kB 🟢 -91.9 kB 🟢 -25.3 kB 🟢 -21.7 kB
assets/api-OUNBZNff.js (new) 91.9 kB 🔴 +91.9 kB 🔴 +25.3 kB 🔴 +21.6 kB
assets/workflowShareService-C6Ydn63o.js (new) 17 kB 🔴 +17 kB 🔴 +5.01 kB 🔴 +4.45 kB
assets/workflowShareService-D0E1Tg7-.js (removed) 17 kB 🟢 -17 kB 🟢 -5.01 kB 🟢 -4.44 kB
assets/releaseStore-2EQmyMva.js (new) 8.29 kB 🔴 +8.29 kB 🔴 +2.34 kB 🔴 +2.05 kB
assets/releaseStore-C8kZORwx.js (removed) 8.29 kB 🟢 -8.29 kB 🟢 -2.34 kB 🟢 -2.05 kB
assets/keybindingService-BusSpkkd.js (removed) 7.46 kB 🟢 -7.46 kB 🟢 -1.92 kB 🟢 -1.64 kB
assets/keybindingService-DOlJRCUH.js (new) 7.46 kB 🔴 +7.46 kB 🔴 +1.92 kB 🔴 +1.65 kB
assets/extensionStore-BBnqkoJO.js (new) 5.29 kB 🔴 +5.29 kB 🔴 +1.86 kB 🔴 +1.58 kB
assets/extensionStore-lU8iLUn5.js (removed) 5.29 kB 🟢 -5.29 kB 🟢 -1.86 kB 🟢 -1.57 kB
assets/userStore-AsWY-0pb.js (new) 2.42 kB 🔴 +2.42 kB 🔴 +932 B 🔴 +818 B
assets/userStore-CQlgpxkj.js (removed) 2.42 kB 🟢 -2.42 kB 🟢 -933 B 🟢 -821 B
assets/audioService-BfUwfj7v.js (removed) 1.76 kB 🟢 -1.76 kB 🟢 -862 B 🟢 -749 B
assets/audioService-D53wjiVF.js (new) 1.76 kB 🔴 +1.76 kB 🔴 +862 B 🔴 +748 B
assets/dialogService-BOCypPiK.js (removed) 100 B 🟢 -100 B 🟢 -99 B 🟢 -93 B
assets/dialogService-CzfulHL3.js (new) 100 B 🔴 +100 B 🔴 +99 B 🔴 +95 B
assets/settingStore-CpJOTZjw.js (new) 98 B 🔴 +98 B 🔴 +98 B 🔴 +99 B
assets/settingStore-CYcVnWBR.js (removed) 98 B 🟢 -98 B 🟢 -98 B 🟢 -91 B
assets/assetsStore-BgTuuBmJ.js (removed) 96 B 🟢 -96 B 🟢 -97 B 🟢 -88 B
assets/assetsStore-DRJsUJtE.js (new) 96 B 🔴 +96 B 🔴 +97 B 🔴 +87 B
assets/releaseStore-DpJgevlc.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +92 B
assets/releaseStore-DrpeGXrL.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -86 B
assets/api-DmaE53gR.js (removed) 62 B 🟢 -62 B 🟢 -74 B 🟢 -66 B
assets/api-DwX919il.js (new) 62 B 🔴 +62 B 🔴 +74 B 🔴 +66 B

Status: 13 added / 13 removed / 3 unchanged

Utilities & Hooks — 3.39 MB (baseline 3.39 MB) • ⚪ 0 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/promotionUtils-CW9GDpWW.js (new) 3.02 MB 🔴 +3.02 MB 🔴 +698 kB 🔴 +526 kB
assets/promotionUtils-DqcXHOnP.js (removed) 3.02 MB 🟢 -3.02 MB 🟢 -698 kB 🟢 -526 kB
assets/useConflictDetection-CXhBsgpT.js (removed) 234 kB 🟢 -234 kB 🟢 -52.3 kB 🟢 -42.6 kB
assets/useConflictDetection-DIzrxOdY.js (new) 234 kB 🔴 +234 kB 🔴 +52.3 kB 🔴 +42.5 kB
assets/useLoad3d-DqVtTN0M.js (new) 25.5 kB 🔴 +25.5 kB 🔴 +5.75 kB 🔴 +5.1 kB
assets/useLoad3d-Jd85K2Fi.js (removed) 25.5 kB 🟢 -25.5 kB 🟢 -5.76 kB 🟢 -5.09 kB
assets/useLoad3dViewer-BOvohgkA.js (removed) 21.1 kB 🟢 -21.1 kB 🟢 -4.97 kB 🟢 -4.37 kB
assets/useLoad3dViewer-BU8TMw4c.js (new) 21.1 kB 🔴 +21.1 kB 🔴 +4.98 kB 🔴 +4.35 kB
assets/useFeatureFlags-BaUzqugM.js (new) 6.51 kB 🔴 +6.51 kB 🔴 +2.02 kB 🔴 +1.71 kB
assets/useFeatureFlags-CXMgKvoG.js (removed) 6.51 kB 🟢 -6.51 kB 🟢 -2.02 kB 🟢 -1.71 kB
assets/subscriptionCheckoutUtil-2WLVXEZK.js (new) 3.85 kB 🔴 +3.85 kB 🔴 +1.54 kB 🔴 +1.35 kB
assets/subscriptionCheckoutUtil-XkPggi98.js (removed) 3.85 kB 🟢 -3.85 kB 🟢 -1.54 kB 🟢 -1.35 kB
assets/useSessionCookie-CAEdH8AB.js (removed) 3.33 kB 🟢 -3.33 kB 🟢 -1.15 kB 🟢 -987 B
assets/useSessionCookie-DtKrzPjl.js (new) 3.33 kB 🔴 +3.33 kB 🔴 +1.15 kB 🔴 +978 B
assets/useDowngradeToPersonal-BIdFs3IM.js (new) 3 kB 🔴 +3 kB 🔴 +1.19 kB 🔴 +1.05 kB
assets/useDowngradeToPersonal-DbKNYA6c.js (removed) 3 kB 🟢 -3 kB 🟢 -1.19 kB 🟢 -1.02 kB
assets/assetPreviewUtil-BYcJPP9t.js (removed) 2.41 kB 🟢 -2.41 kB 🟢 -1 kB 🟢 -880 B
assets/assetPreviewUtil-DPuAamXo.js (new) 2.41 kB 🔴 +2.41 kB 🔴 +1.01 kB 🔴 +877 B
assets/useUpstreamValue-CzbbSIWn.js (new) 2.04 kB 🔴 +2.04 kB 🔴 +793 B 🔴 +707 B
assets/useUpstreamValue-DMyIiWC8.js (removed) 2.04 kB 🟢 -2.04 kB 🟢 -794 B 🟢 -706 B
assets/useWorkspaceTierLabel-BD0I6LoP.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -853 B 🟢 -733 B
assets/useWorkspaceTierLabel-fZlZ2_i5.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +855 B 🔴 +739 B
assets/useLoad3d-BBUqbPxb.js (removed) 311 B 🟢 -311 B 🟢 -165 B 🟢 -147 B
assets/useLoad3d-DEpdsp1j.js (new) 311 B 🔴 +311 B 🔴 +162 B 🔴 +147 B
assets/useSessionCookie-D6FmP0uh.js (removed) 101 B 🟢 -101 B 🟢 -86 B 🟢 -83 B
assets/useSessionCookie-DpIMfKpy.js (new) 101 B 🔴 +101 B 🔴 +86 B 🔴 +79 B
assets/useFeatureFlags-BincRgjT.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +84 B
assets/useFeatureFlags-DNSGqL8c.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -83 B
assets/useLoad3dViewer-CDg_IdUL.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -85 B
assets/useLoad3dViewer-D7Q4Cc_W.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +85 B
assets/useCurrentUser-BE0ucie3.js (new) 96 B 🔴 +96 B 🔴 +97 B 🔴 +93 B
assets/useCurrentUser-D82SPyjG.js (removed) 96 B 🟢 -96 B 🟢 -97 B 🟢 -85 B

Status: 16 added / 16 removed / 18 unchanged

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

External libraries and shared vendor chunks

Status: 16 unchanged

Other — 11.7 MB (baseline 11.7 MB) • ⚪ 0 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-DBMps73Q.js (new) 119 kB 🔴 +119 kB 🔴 +30.7 kB 🔴 +25.9 kB
assets/core-DjxLb9vY.js (removed) 119 kB 🟢 -119 kB 🟢 -30.7 kB 🟢 -26 kB
assets/WidgetSelect-C5aj5gMl.js (removed) 89.3 kB 🟢 -89.3 kB 🟢 -20.1 kB 🟢 -17.3 kB
assets/WidgetSelect-DgF73CnZ.js (new) 89.3 kB 🔴 +89.3 kB 🔴 +20.1 kB 🔴 +17.2 kB
assets/Load3DControls-BWphB9ud.js (removed) 46.8 kB 🟢 -46.8 kB 🟢 -7.57 kB 🟢 -6.62 kB
assets/Load3DControls-DYmlaZTO.js (new) 46.8 kB 🔴 +46.8 kB 🔴 +7.57 kB 🔴 +6.62 kB
assets/SubscriptionTransitionPreviewWorkspace-1wIc1RK_.js (removed) 46.8 kB 🟢 -46.8 kB 🟢 -9.65 kB 🟢 -8.54 kB
assets/SubscriptionTransitionPreviewWorkspace-CG7MtH1l.js (new) 46.8 kB 🔴 +46.8 kB 🔴 +9.65 kB 🔴 +8.56 kB
assets/SubscriptionRequiredDialogContentUnified-BmZqXSrk.js (removed) 41.2 kB 🟢 -41.2 kB 🟢 -9.16 kB 🟢 -7.99 kB
assets/SubscriptionRequiredDialogContentUnified-DWjEA3oM.js (new) 41.2 kB 🔴 +41.2 kB 🔴 +9.16 kB 🔴 +7.98 kB
assets/WorkspacePanelContent-ChVRtRIU.js (removed) 34.5 kB 🟢 -34.5 kB 🟢 -7.5 kB 🟢 -6.61 kB
assets/WorkspacePanelContent-DsJNRgoQ.js (new) 34.5 kB 🔴 +34.5 kB 🔴 +7.5 kB 🔴 +6.61 kB
assets/WidgetPainter-CQCMQY1r.js (new) 32.7 kB 🔴 +32.7 kB 🔴 +7.91 kB 🔴 +7 kB
assets/WidgetPainter-Tf37Hu0a.js (removed) 32.7 kB 🟢 -32.7 kB 🟢 -7.9 kB 🟢 -6.99 kB
assets/Load3dViewerContent-DpMdtO1k.js (removed) 30.9 kB 🟢 -30.9 kB 🟢 -6.3 kB 🟢 -5.46 kB
assets/Load3dViewerContent-XJi8YLI6.js (new) 30.9 kB 🔴 +30.9 kB 🔴 +6.3 kB 🔴 +5.46 kB
assets/WidgetBoundingBoxes-DNwRby8N.js (new) 28.6 kB 🔴 +28.6 kB 🔴 +7.9 kB 🔴 +7.01 kB
assets/WidgetBoundingBoxes-Dtbe-n3h.js (removed) 28.6 kB 🟢 -28.6 kB 🟢 -7.9 kB 🟢 -7.01 kB
assets/SubscriptionRequiredDialogContent-BNoCuL50.js (removed) 27.4 kB 🟢 -27.4 kB 🟢 -6.75 kB 🟢 -5.96 kB
assets/SubscriptionRequiredDialogContent-DDmWsa9G.js (new) 27.4 kB 🔴 +27.4 kB 🔴 +6.75 kB 🔴 +5.96 kB
assets/SubscriptionPanelContentWorkspace-D9Df-MkR.js (removed) 25 kB 🟢 -25 kB 🟢 -5.84 kB 🟢 -5.13 kB
assets/SubscriptionPanelContentWorkspace-DG0gZXwn.js (new) 25 kB 🔴 +25 kB 🔴 +5.84 kB 🔴 +5.14 kB
assets/SubscriptionRequiredDialogContentWorkspace-CwMbkBNv.js (removed) 24.6 kB 🟢 -24.6 kB 🟢 -5.66 kB 🟢 -4.98 kB
assets/SubscriptionRequiredDialogContentWorkspace-DpZ_8Q1Y.js (new) 24.6 kB 🔴 +24.6 kB 🔴 +5.66 kB 🔴 +4.97 kB
assets/WidgetImageCrop-DAgSSkMn.js (new) 23.3 kB 🔴 +23.3 kB 🔴 +5.75 kB 🔴 +5.05 kB
assets/WidgetImageCrop-RBN46ZJy.js (removed) 23.3 kB 🟢 -23.3 kB 🟢 -5.76 kB 🟢 -5.05 kB
assets/load3d-CrmZpsz0.js (new) 21.3 kB 🔴 +21.3 kB 🔴 +5.19 kB 🔴 +4.51 kB
assets/load3d-RZiwwk3e.js (removed) 21.3 kB 🟢 -21.3 kB 🟢 -5.19 kB 🟢 -4.5 kB
assets/CurrentUserPopoverWorkspace-BnAp0W0W.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +4.75 kB 🔴 +4.24 kB
assets/CurrentUserPopoverWorkspace-De6VqEcu.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -4.75 kB 🟢 -4.24 kB
assets/SignInContent-D2JQX8Bi.js (removed) 20.1 kB 🟢 -20.1 kB 🟢 -5.07 kB 🟢 -4.43 kB
assets/SignInContent-DW2G2ZRf.js (new) 20.1 kB 🔴 +20.1 kB 🔴 +5.07 kB 🔴 +4.43 kB
assets/WidgetInputNumber-C_YJ1NQ_.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.82 kB 🟢 -4.28 kB
assets/WidgetInputNumber-mipZ_BuW.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.82 kB 🔴 +4.28 kB
assets/Load3D-BaZjbAa9.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.52 kB 🟢 -3.94 kB
assets/Load3D-X47hMaZM.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.52 kB 🔴 +3.94 kB
assets/CreditsTile-DdVx3rlj.js (new) 17.1 kB 🔴 +17.1 kB 🔴 +4.54 kB 🔴 +4.01 kB
assets/CreditsTile-DoItZttn.js (removed) 17.1 kB 🟢 -17.1 kB 🟢 -4.54 kB 🟢 -4.01 kB
assets/WidgetRecordAudio-3d9SSzR5.js (new) 16.6 kB 🔴 +16.6 kB 🔴 +4.63 kB 🔴 +4.14 kB
assets/WidgetRecordAudio-C50P8UGl.js (removed) 16.6 kB 🟢 -16.6 kB 🟢 -4.63 kB 🟢 -4.14 kB
assets/WidgetRange-BJ3p3tRb.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.17 kB 🔴 +3.73 kB
assets/WidgetRange-CpNag4PL.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.17 kB 🟢 -3.73 kB
assets/WaveAudioPlayer-CUf8RNX8.js (new) 12.8 kB 🔴 +12.8 kB 🔴 +3.49 kB 🔴 +3.06 kB
assets/WaveAudioPlayer-DWFrfrjk.js (removed) 12.8 kB 🟢 -12.8 kB 🟢 -3.48 kB 🟢 -3.06 kB
assets/WidgetCurve-BmePIwoj.js (new) 11.3 kB 🔴 +11.3 kB 🔴 +3.5 kB 🔴 +3.16 kB
assets/WidgetCurve-CkpLu6xA.js (removed) 11.3 kB 🟢 -11.3 kB 🟢 -3.5 kB 🟢 -3.16 kB
assets/TeamWorkspacesDialogContent-BdqvLeIW.js (removed) 10.3 kB 🟢 -10.3 kB 🟢 -3 kB 🟢 -2.66 kB
assets/TeamWorkspacesDialogContent-BQ-TyHg7.js (new) 10.3 kB 🔴 +10.3 kB 🔴 +3 kB 🔴 +2.66 kB
assets/Load3DConfiguration-Bo4e9DSS.js (new) 9.02 kB 🔴 +9.02 kB 🔴 +2.67 kB 🔴 +2.35 kB
assets/Load3DConfiguration-DUOLURxO.js (removed) 9.02 kB 🟢 -9.02 kB 🟢 -2.67 kB 🟢 -2.35 kB
assets/onboardingCloudRoutes-QWrI29EI.js (new) 8.35 kB 🔴 +8.35 kB 🔴 +2.61 kB 🔴 +2.24 kB
assets/onboardingCloudRoutes-YT_A46Za.js (removed) 8.35 kB 🟢 -8.35 kB 🟢 -2.61 kB 🟢 -2.24 kB
assets/nodeTemplates-D4G-GQav.js (removed) 8.33 kB 🟢 -8.33 kB 🟢 -2.88 kB 🟢 -2.54 kB
assets/nodeTemplates-uE57C4sw.js (new) 8.33 kB 🔴 +8.33 kB 🔴 +2.88 kB 🔴 +2.54 kB
assets/NightlySurveyController-CrnRglL6.js (new) 7.95 kB 🔴 +7.95 kB 🔴 +2.7 kB 🔴 +2.37 kB
assets/NightlySurveyController-SXoPSEoW.js (removed) 7.95 kB 🟢 -7.95 kB 🟢 -2.7 kB 🟢 -2.37 kB
assets/InviteMemberDialogContent-CqMfhLfJ.js (removed) 6.76 kB 🟢 -6.76 kB 🟢 -2.22 kB 🟢 -1.96 kB
assets/InviteMemberDialogContent-Df0YRaLV.js (new) 6.76 kB 🔴 +6.76 kB 🔴 +2.22 kB 🔴 +1.96 kB
assets/WidgetWithControl-CKuBnUrm.js (new) 6.34 kB 🔴 +6.34 kB 🔴 +2.57 kB 🔴 +2.28 kB
assets/WidgetWithControl-DxmhL1HN.js (removed) 6.34 kB 🟢 -6.34 kB 🟢 -2.56 kB 🟢 -2.27 kB
assets/tierBenefits-BTWE1r2Q.js (removed) 6.02 kB 🟢 -6.02 kB 🟢 -1.93 kB 🟢 -1.67 kB
assets/tierBenefits-Dcn-EeBa.js (new) 6.02 kB 🔴 +6.02 kB 🔴 +1.93 kB 🔴 +1.67 kB
assets/CancelSubscriptionDialogContent-CiP-r3zV.js (new) 5.76 kB 🔴 +5.76 kB 🔴 +1.98 kB 🔴 +1.73 kB
assets/CancelSubscriptionDialogContent-DgH1Z62a.js (removed) 5.76 kB 🟢 -5.76 kB 🟢 -1.98 kB 🟢 -1.73 kB
assets/load3dPreviewExtensions-CakhgBBB.js (new) 5.38 kB 🔴 +5.38 kB 🔴 +1.75 kB 🔴 +1.55 kB
assets/load3dPreviewExtensions-CEVh8mI1.js (removed) 5.38 kB 🟢 -5.38 kB 🟢 -1.75 kB 🟢 -1.55 kB
assets/FreeTierDialogContent-DIwFZh0I.js (removed) 5.25 kB 🟢 -5.25 kB 🟢 -1.76 kB 🟢 -1.56 kB
assets/FreeTierDialogContent-t7mra_T5.js (new) 5.25 kB 🔴 +5.25 kB 🔴 +1.76 kB 🔴 +1.55 kB
assets/CreateWorkspaceDialogContent-D12EcSvr.js (removed) 5.19 kB 🟢 -5.19 kB 🟢 -1.83 kB 🟢 -1.58 kB
assets/CreateWorkspaceDialogContent-DRRHU5iC.js (new) 5.19 kB 🔴 +5.19 kB 🔴 +1.83 kB 🔴 +1.59 kB
assets/missingModelDownload-BhtvmBCV.js (new) 5.07 kB 🔴 +5.07 kB 🔴 +1.98 kB 🔴 +1.72 kB
assets/missingModelDownload-CjG8aNLG.js (removed) 5.07 kB 🟢 -5.07 kB 🟢 -1.98 kB 🟢 -1.72 kB
assets/ChangeMemberRoleDialogContent-BOgQfKcV.js (removed) 5.04 kB 🟢 -5.04 kB 🟢 -1.67 kB 🟢 -1.46 kB
assets/ChangeMemberRoleDialogContent-BV_mtelk.js (new) 5.04 kB 🔴 +5.04 kB 🔴 +1.67 kB 🔴 +1.46 kB
assets/EditWorkspaceDialogContent-BR33g2Wz.js (removed) 5 kB 🟢 -5 kB 🟢 -1.79 kB 🟢 -1.55 kB
assets/EditWorkspaceDialogContent-SWtpnKrz.js (new) 5 kB 🔴 +5 kB 🔴 +1.79 kB 🔴 +1.56 kB
assets/WidgetTextarea-BTDRMr3q.js (new) 4.86 kB 🔴 +4.86 kB 🔴 +1.9 kB 🔴 +1.67 kB
assets/WidgetTextarea-E8mPWVOb.js (removed) 4.86 kB 🟢 -4.86 kB 🟢 -1.9 kB 🟢 -1.66 kB
assets/saveMesh-Bg2KHitp.js (removed) 4.81 kB 🟢 -4.81 kB 🟢 -1.56 kB 🟢 -1.37 kB
assets/saveMesh-DJanwmQM.js (new) 4.81 kB 🔴 +4.81 kB 🔴 +1.56 kB 🔴 +1.38 kB
assets/Preview3d-CTda0vC9.js (new) 4.59 kB 🔴 +4.59 kB 🔴 +1.43 kB 🔴 +1.23 kB
assets/Preview3d-T7cjpKRM.js (removed) 4.59 kB 🟢 -4.59 kB 🟢 -1.43 kB 🟢 -1.23 kB
assets/ValueControlPopover-C-XwxMTh.js (new) 4.55 kB 🔴 +4.55 kB 🔴 +1.59 kB 🔴 +1.41 kB
assets/ValueControlPopover-C45__-mT.js (removed) 4.55 kB 🟢 -4.55 kB 🟢 -1.59 kB 🟢 -1.42 kB
assets/DeleteWorkspaceDialogContent-B3TUBhCk.js (removed) 3.91 kB 🟢 -3.91 kB 🟢 -1.47 kB 🟢 -1.27 kB
assets/DeleteWorkspaceDialogContent-BZfXtXbV.js (new) 3.91 kB 🔴 +3.91 kB 🔴 +1.47 kB 🔴 +1.27 kB
assets/LeaveWorkspaceDialogContent-CXVs6GIf.js (new) 3.73 kB 🔴 +3.73 kB 🔴 +1.42 kB 🔴 +1.22 kB
assets/LeaveWorkspaceDialogContent-HpoYOo4i.js (removed) 3.73 kB 🟢 -3.73 kB 🟢 -1.41 kB 🟢 -1.22 kB
assets/RemoveMemberDialogContent-AnZMsdUU.js (removed) 3.71 kB 🟢 -3.71 kB 🟢 -1.37 kB 🟢 -1.19 kB
assets/RemoveMemberDialogContent-BMn9u2xT.js (new) 3.71 kB 🔴 +3.71 kB 🔴 +1.37 kB 🔴 +1.19 kB
assets/RevokeInviteDialogContent-B-XzNr-u.js (removed) 3.63 kB 🟢 -3.63 kB 🟢 -1.38 kB 🟢 -1.2 kB
assets/RevokeInviteDialogContent-D8aCdpVa.js (new) 3.63 kB 🔴 +3.63 kB 🔴 +1.38 kB 🔴 +1.21 kB
assets/InviteMemberUpsellDialogContent-D6qkPcS9.js (removed) 3.52 kB 🟢 -3.52 kB 🟢 -1.27 kB 🟢 -1.1 kB
assets/InviteMemberUpsellDialogContent-Dt15wU2r.js (new) 3.52 kB 🔴 +3.52 kB 🔴 +1.27 kB 🔴 +1.12 kB
assets/workspaceCheckoutTelemetry-CWv4n_hg.js (new) 3.47 kB 🔴 +3.47 kB 🔴 +1.56 kB 🔴 +1.36 kB
assets/workspaceCheckoutTelemetry-Gz9KKHbI.js (removed) 3.47 kB 🟢 -3.47 kB 🟢 -1.56 kB 🟢 -1.36 kB
assets/Media3DTop-CVhVfzXT.js (removed) 3.26 kB 🟢 -3.26 kB 🟢 -1.3 kB 🟢 -1.13 kB
assets/Media3DTop-DzUQmh9o.js (new) 3.26 kB 🔴 +3.26 kB 🔴 +1.3 kB 🔴 +1.13 kB
assets/GlobalToast-B7gZ6kZ3.js (removed) 3.05 kB 🟢 -3.05 kB 🟢 -1.26 kB 🟢 -1.08 kB
assets/GlobalToast-DNTg36at.js (new) 3.05 kB 🔴 +3.05 kB 🔴 +1.26 kB 🔴 +1.08 kB
assets/load3dAdvanced-C13I3F19.js (removed) 2.87 kB 🟢 -2.87 kB 🟢 -1.13 kB 🟢 -984 B
assets/load3dAdvanced-zRiBxTnY.js (new) 2.87 kB 🔴 +2.87 kB 🔴 +1.13 kB 🔴 +995 B
assets/SubscribeToRun-BYUV4nC7.js (removed) 2.56 kB 🟢 -2.56 kB 🟢 -1.11 kB 🟢 -978 B
assets/SubscribeToRun-Df70WPl1.js (new) 2.56 kB 🔴 +2.56 kB 🔴 +1.11 kB 🔴 +975 B
assets/graphHasMissingNodes-CiEuBzpO.js (new) 1.93 kB 🔴 +1.93 kB 🔴 +908 B 🔴 +788 B
assets/graphHasMissingNodes-CTmUvPem.js (removed) 1.93 kB 🟢 -1.93 kB 🟢 -907 B 🟢 -802 B
assets/MediaAudioTop-DHx4Zg88.js (new) 1.67 kB 🔴 +1.67 kB 🔴 +837 B 🔴 +698 B
assets/MediaAudioTop-PHEVs8Va.js (removed) 1.67 kB 🟢 -1.67 kB 🟢 -835 B 🟢 -701 B
assets/CloudRunButtonWrapper-C4BEEOZM.js (removed) 1.13 kB 🟢 -1.13 kB 🟢 -551 B 🟢 -519 B
assets/CloudRunButtonWrapper-zqKr6UCp.js (new) 1.13 kB 🔴 +1.13 kB 🔴 +550 B 🔴 +512 B
assets/cloudSessionCookie-CaO7c66h.js (removed) 991 B 🟢 -991 B 🟢 -467 B 🟢 -415 B
assets/cloudSessionCookie-CO9rbz0N.js (new) 991 B 🔴 +991 B 🔴 +465 B 🔴 +410 B
assets/cloudBadges-Bi9B2TTa.js (new) 973 B 🔴 +973 B 🔴 +551 B 🔴 +500 B
assets/cloudBadges-BUvVkLrv.js (removed) 973 B 🟢 -973 B 🟢 -552 B 🟢 -468 B
assets/Load3DAdvanced-CRMbf5BS.js (removed) 813 B 🟢 -813 B 🟢 -455 B 🟢 -401 B
assets/Load3DAdvanced-DykleeQP.js (new) 813 B 🔴 +813 B 🔴 +454 B 🔴 +414 B
assets/nightlyBadges-B4f_oHfQ.js (new) 464 B 🔴 +464 B 🔴 +307 B 🔴 +255 B
assets/nightlyBadges-Cu9b4-0I.js (removed) 464 B 🟢 -464 B 🟢 -307 B 🟢 -255 B
assets/missingModelDownload-gjP8jV6Y.js (new) 228 B 🔴 +228 B 🔴 +147 B 🔴 +127 B
assets/missingModelDownload-kHGY5YWm.js (removed) 228 B 🟢 -228 B 🟢 -150 B 🟢 -129 B
assets/SubscriptionPanelContentWorkspace-B3kTDNcB.js (removed) 179 B 🟢 -179 B 🟢 -117 B 🟢 -90 B
assets/SubscriptionPanelContentWorkspace-DvNJN6Xi.js (new) 179 B 🔴 +179 B 🔴 +117 B 🔴 +92 B
assets/Load3dViewerContent-Dtvl2Pnb.js (removed) 137 B 🟢 -137 B 🟢 -103 B 🟢 -91 B
assets/Load3dViewerContent-fUoRSBou.js (new) 137 B 🔴 +137 B 🔴 +103 B 🔴 +94 B
assets/Load3DAdvanced-CbtNlHjO.js (removed) 122 B 🟢 -122 B 🟢 -97 B 🟢 -87 B
assets/Load3DAdvanced-e7Dz4GxM.js (new) 122 B 🔴 +122 B 🔴 +97 B 🔴 +88 B
assets/WidgetLegacy-C5YKnp2O.js (removed) 119 B 🟢 -119 B 🟢 -108 B 🟢 -97 B
assets/WidgetLegacy-CV_entL6.js (new) 119 B 🔴 +119 B 🔴 +108 B 🔴 +93 B
assets/workflowDraftStoreV2-BXstV-cM.js (new) 113 B 🔴 +113 B 🔴 +105 B 🔴 +108 B
assets/workflowDraftStoreV2-ByoLbdDf.js (removed) 113 B 🟢 -113 B 🟢 -105 B 🟢 -106 B
assets/Load3D-2sQh1Bew.js (removed) 98 B 🟢 -98 B 🟢 -89 B 🟢 -85 B
assets/Load3D-D3H5mdLo.js (new) 98 B 🔴 +98 B 🔴 +89 B 🔴 +82 B
assets/changeTracker-BznKvAYm.js (removed) 93 B 🟢 -93 B 🟢 -95 B 🟢 -85 B
assets/changeTracker-DM1gwIVZ.js (new) 93 B 🔴 +93 B 🔴 +95 B 🔴 +80 B

Status: 67 added / 67 removed / 98 unchanged

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 62.2 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 50.2 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 52.9 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.9 MB heap
large-graph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 56.9 MB heap
large-graph-pan: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 76.5 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.2 MB heap
minimap-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 63.0 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 45.4 MB heap
subgraph-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 63.4 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 58.6 MB heap
subgraph-transition-enter: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 101ms TBT · 96.9 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 77.2 MB heap
vue-large-graph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 161.7 MB heap
vue-large-graph-pan: · 58.1 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 14ms TBT · 164.5 MB heap
workflow-execution: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 46.3 MB heap

⚠️ 1 regression detected

Show regressions
Metric Baseline PR (median) Δ Sig
workflow-execution: task duration 125ms 189ms +52% ⚠️ z=6.1
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 +0%
canvas-idle: layout duration 0ms 0ms +0%
canvas-idle: style recalc duration 8ms 7ms -13% z=-4.3
canvas-idle: layout count 0 0 +0%
canvas-idle: style recalc count 9 10 +6% z=-2.9
canvas-idle: task duration 411ms 333ms -19% z=-2.0
canvas-idle: script duration 15ms 15ms -1% z=-4.7
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: heap used 59.2 MB 62.2 MB +5%
canvas-idle: DOM nodes -284 -129 -55% z=-118.9
canvas-idle: event listeners -199 -95 -53% z=-22.4
canvas-mouse-sweep: avg frame time 17ms 17ms +0% z=-0.4
canvas-mouse-sweep: p95 frame time 17ms 17ms +0%
canvas-mouse-sweep: layout duration 4ms 3ms -18% z=-3.3
canvas-mouse-sweep: style recalc duration 38ms 30ms -20% z=-3.9
canvas-mouse-sweep: layout count 12 12 +0%
canvas-mouse-sweep: style recalc count 76 72 -5% z=-2.8
canvas-mouse-sweep: task duration 810ms 622ms -23% z=-4.2
canvas-mouse-sweep: script duration 120ms 88ms -27% z=-7.4
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: heap used 53.8 MB 50.2 MB -7%
canvas-mouse-sweep: DOM nodes -242 -256 +6% z=-122.9
canvas-mouse-sweep: event listeners -199 -198 -1% z=-49.5
canvas-zoom-sweep: avg frame time 17ms 17ms +0% z=0.5
canvas-zoom-sweep: p95 frame time 17ms 17ms +0%
canvas-zoom-sweep: layout duration 1ms 1ms -11% z=-1.4
canvas-zoom-sweep: style recalc duration 17ms 14ms -16% z=-3.2
canvas-zoom-sweep: layout count 6 6 +0%
canvas-zoom-sweep: style recalc count 32 31 -3% z=-0.6
canvas-zoom-sweep: task duration 327ms 295ms -10% z=-1.4
canvas-zoom-sweep: script duration 18ms 19ms +9% z=-2.6
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: heap used 65.5 MB 52.9 MB -19%
canvas-zoom-sweep: DOM nodes -211 -81 -62% z=-201.8
canvas-zoom-sweep: event listeners -184 -84 -55% z=-20.7
dom-widget-clipping: avg frame time 17ms 17ms -0% z=-0.2
dom-widget-clipping: p95 frame time 17ms 17ms -1%
dom-widget-clipping: layout duration 0ms 0ms +0%
dom-widget-clipping: style recalc duration 10ms 8ms -20% z=-2.1
dom-widget-clipping: layout count 0 0 +0%
dom-widget-clipping: style recalc count 11 12 +9% z=-2.2
dom-widget-clipping: task duration 385ms 285ms -26% z=-4.9
dom-widget-clipping: script duration 61ms 42ms -30% z=-7.8
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: heap used 43.7 MB 53.9 MB +23%
dom-widget-clipping: DOM nodes -281 -127 -55% z=-104.8
dom-widget-clipping: event listeners -203 -99 -51% variance too high
large-graph-idle: avg frame time 17ms 17ms +0% z=-0.2
large-graph-idle: p95 frame time 17ms 17ms +0%
large-graph-idle: layout duration 0ms 0ms +0%
large-graph-idle: style recalc duration 9ms 6ms -29% z=-5.5
large-graph-idle: layout count 0 0 +0%
large-graph-idle: style recalc count 10 9 -10% z=-8.3
large-graph-idle: task duration 545ms 419ms -23% z=-2.3
large-graph-idle: script duration 92ms 73ms -21% z=-2.8
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: heap used 59.6 MB 56.9 MB -5%
large-graph-idle: DOM nodes -263 -267 +1% z=-322.2
large-graph-idle: event listeners -130 -96 -27% z=-20.0
large-graph-pan: avg frame time 17ms 17ms +0% z=0.3
large-graph-pan: p95 frame time 17ms 17ms +1%
large-graph-pan: layout duration 0ms 0ms +0%
large-graph-pan: style recalc duration 19ms 16ms -14% z=-1.4
large-graph-pan: layout count 0 0 +0%
large-graph-pan: style recalc count 69 68 -1% z=-2.4
large-graph-pan: task duration 1064ms 866ms -19% z=-5.0
large-graph-pan: script duration 382ms 305ms -20% z=-5.2
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: heap used 80.0 MB 76.5 MB -4%
large-graph-pan: DOM nodes -263 -273 +4% z=-177.0
large-graph-pan: event listeners -126 -96 -24% z=-121.3
large-graph-zoom: avg frame time 17ms 17ms -0%
large-graph-zoom: p95 frame time 17ms 17ms +1%
large-graph-zoom: layout duration 7ms 8ms +8%
large-graph-zoom: style recalc duration 17ms 19ms +13%
large-graph-zoom: layout count 60 60 +0%
large-graph-zoom: style recalc count 64 66 +3%
large-graph-zoom: task duration 1243ms 1105ms -11%
large-graph-zoom: script duration 475ms 421ms -12%
large-graph-zoom: TBT 0ms 0ms +0%
large-graph-zoom: heap used 65.6 MB 65.2 MB -1%
large-graph-zoom: DOM nodes 10 -130 -1395%
large-graph-zoom: event listeners 8 7 -13%
minimap-idle: avg frame time 17ms 17ms -0% z=0.1
minimap-idle: p95 frame time 17ms 17ms -0%
minimap-idle: layout duration 0ms 0ms +0%
minimap-idle: style recalc duration 5ms 6ms +30% z=-4.2
minimap-idle: layout count 0 0 +0%
minimap-idle: style recalc count 6 9 +42% z=-1.6
minimap-idle: task duration 480ms 470ms -2% z=-1.2
minimap-idle: script duration 84ms 77ms -8% z=-2.1
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: heap used 56.6 MB 63.0 MB +11%
minimap-idle: DOM nodes -274 -124 -55% z=-104.3
minimap-idle: event listeners -199 -97 -52% z=-153.1
subgraph-dom-widget-clipping: avg frame time 17ms 17ms +0% z=0.1
subgraph-dom-widget-clipping: p95 frame time 17ms 17ms -0%
subgraph-dom-widget-clipping: layout duration 0ms 0ms +0%
subgraph-dom-widget-clipping: style recalc duration 11ms 10ms -3% z=-2.4
subgraph-dom-widget-clipping: layout count 0 0 +0%
subgraph-dom-widget-clipping: style recalc count 47 47 +0% z=-1.6
subgraph-dom-widget-clipping: task duration 407ms 335ms -18% z=-2.4
subgraph-dom-widget-clipping: script duration 119ms 99ms -17% z=-4.6
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: heap used 58.6 MB 45.4 MB -23%
subgraph-dom-widget-clipping: DOM nodes -289 -273 -6% z=-263.4
subgraph-dom-widget-clipping: event listeners -197 -193 -2% z=-35.9
subgraph-idle: avg frame time 17ms 17ms -0% z=-0.7
subgraph-idle: p95 frame time 17ms 17ms +1%
subgraph-idle: layout duration 0ms 0ms +0%
subgraph-idle: style recalc duration 9ms 8ms -16% z=-3.2
subgraph-idle: layout count 0 0 +0%
subgraph-idle: style recalc count 11 12 +5% z=0.9
subgraph-idle: task duration 388ms 341ms -12% z=-0.9
subgraph-idle: script duration 14ms 13ms -5% z=-2.6
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: heap used 48.6 MB 63.4 MB +30%
subgraph-idle: DOM nodes -287 -282 -2% z=-202.9
subgraph-idle: event listeners -199 -196 -2% 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.0
subgraph-mouse-sweep: style recalc duration 37ms 30ms -18% z=-3.6
subgraph-mouse-sweep: layout count 16 16 +0%
subgraph-mouse-sweep: style recalc count 75 79 +5% z=-0.8
subgraph-mouse-sweep: task duration 683ms 532ms -22% z=-3.4
subgraph-mouse-sweep: script duration 86ms 73ms -16% z=-4.3
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: heap used 52.8 MB 58.6 MB +11%
subgraph-mouse-sweep: DOM nodes -227 66 -129% z=-0.4
subgraph-mouse-sweep: event listeners -199 4 -102% 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 16ms 12ms -25%
subgraph-transition-enter: style recalc duration 32ms 26ms -19%
subgraph-transition-enter: layout count 14 16 +14%
subgraph-transition-enter: style recalc count 18 20 +11%
subgraph-transition-enter: task duration 750ms 647ms -14%
subgraph-transition-enter: script duration 28ms 25ms -11%
subgraph-transition-enter: TBT 165ms 101ms -39%
subgraph-transition-enter: heap used 75.4 MB 96.9 MB +28%
subgraph-transition-enter: DOM nodes 13673 13673 +0%
subgraph-transition-enter: event listeners 2533 2531 -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 52ms 55ms +7%
viewport-pan-sweep: layout count 0 0 +0%
viewport-pan-sweep: style recalc count 250 252 +1%
viewport-pan-sweep: task duration 3708ms 3172ms -14%
viewport-pan-sweep: script duration 1273ms 1025ms -20%
viewport-pan-sweep: TBT 0ms 0ms +0%
viewport-pan-sweep: heap used 66.4 MB 77.2 MB +16%
viewport-pan-sweep: DOM nodes -271 -120 -56%
viewport-pan-sweep: event listeners -183 -80 -57%
vue-large-graph-idle: avg frame time 17ms 17ms -3%
vue-large-graph-idle: p95 frame time 17ms 17ms -1%
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 12015ms 11164ms -7%
vue-large-graph-idle: script duration 523ms 445ms -15%
vue-large-graph-idle: TBT 0ms 0ms +0%
vue-large-graph-idle: heap used 162.7 MB 161.7 MB -1%
vue-large-graph-idle: DOM nodes -3300 -5808 +76%
vue-large-graph-idle: event listeners -16378 -16370 -0%
vue-large-graph-pan: avg frame time 17ms 17ms +0%
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 17ms 18ms +1%
vue-large-graph-pan: layout count 0 0 +0%
vue-large-graph-pan: style recalc count 74 66 -11%
vue-large-graph-pan: task duration 15077ms 13715ms -9%
vue-large-graph-pan: script duration 813ms 694ms -15%
vue-large-graph-pan: TBT 44ms 14ms -69%
vue-large-graph-pan: heap used 166.9 MB 164.5 MB -1%
vue-large-graph-pan: DOM nodes -3301 -3299 -0%
vue-large-graph-pan: event listeners -16376 -16375 -0%
workflow-execution: avg frame time 17ms 17ms -0% z=0.1
workflow-execution: p95 frame time 17ms 17ms +0%
workflow-execution: layout duration 1ms 1ms -10% z=-3.7
workflow-execution: style recalc duration 22ms 22ms +2% z=-0.9
workflow-execution: layout count 4 4 +0% z=-1.7
workflow-execution: style recalc count 16 18 +9% z=-0.2
workflow-execution: task duration 125ms 189ms +52% ⚠️ z=6.1
workflow-execution: script duration 17ms 17ms -3% z=-4.1
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: heap used 55.5 MB 46.3 MB -16%
workflow-execution: DOM nodes 155 -169 -209% z=-46.4
workflow-execution: event listeners 69 -132 -291% z=-42.1
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-07-09T02:27:34.138Z",
  "gitSha": "f02830fe00f0d2b0de2687343f356545e92fe58b",
  "branch": "nathaniel/custom-node-e2e-suite",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2036.231000000015,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 5.612000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 315.16299999999995,
      "heapDeltaBytes": 3041064,
      "heapUsedBytes": 68067764,
      "domNodes": 18,
      "jsHeapTotalBytes": 22507520,
      "scriptDurationMs": 13.613,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "canvas-idle",
      "durationMs": 2024.1770000000088,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 8.354000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 351.142,
      "heapDeltaBytes": -7327980,
      "heapUsedBytes": 62304620,
      "domNodes": -276,
      "jsHeapTotalBytes": 19550208,
      "scriptDurationMs": 15.767000000000003,
      "eventListeners": -195,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1726.173999999986,
      "styleRecalcs": 72,
      "styleRecalcDurationMs": 30.956999999999997,
      "layouts": 12,
      "layoutDurationMs": 2.754,
      "taskDurationMs": 600.916,
      "heapDeltaBytes": -12638356,
      "heapUsedBytes": 56832500,
      "domNodes": -241,
      "jsHeapTotalBytes": 19812352,
      "scriptDurationMs": 82.494,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1736.1099999999965,
      "styleRecalcs": 72,
      "styleRecalcDurationMs": 29.48,
      "layouts": 12,
      "layoutDurationMs": 3.1639999999999997,
      "taskDurationMs": 642.365,
      "heapDeltaBytes": -3689756,
      "heapUsedBytes": 48466968,
      "domNodes": -270,
      "jsHeapTotalBytes": 16117760,
      "scriptDurationMs": 92.977,
      "eventListeners": -197,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1744.0860000000384,
      "styleRecalcs": 30,
      "styleRecalcDurationMs": 12.721000000000002,
      "layouts": 6,
      "layoutDurationMs": 0.5310000000000001,
      "taskDurationMs": 295.943,
      "heapDeltaBytes": -2991488,
      "heapUsedBytes": 49447860,
      "domNodes": -239,
      "jsHeapTotalBytes": 9302016,
      "scriptDurationMs": 16.174,
      "eventListeners": -186,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1708.235000000002,
      "styleRecalcs": 32,
      "styleRecalcDurationMs": 15.7,
      "layouts": 6,
      "layoutDurationMs": 0.626,
      "taskDurationMs": 293.582,
      "heapDeltaBytes": 2321300,
      "heapUsedBytes": 61498348,
      "domNodes": 78,
      "jsHeapTotalBytes": 24641536,
      "scriptDurationMs": 22.384999999999998,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 430.01700000002074,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 6.383000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 263.77799999999996,
      "heapDeltaBytes": 7047772,
      "heapUsedBytes": 66215248,
      "domNodes": 20,
      "jsHeapTotalBytes": 19136512,
      "scriptDurationMs": 42.789,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 486.29199999999173,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 10.052999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 305.519,
      "heapDeltaBytes": -22911972,
      "heapUsedBytes": 46727348,
      "domNodes": -273,
      "jsHeapTotalBytes": 6180864,
      "scriptDurationMs": 42.097,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2036.0959999999864,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 6.761000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 427.61499999999995,
      "heapDeltaBytes": -25766312,
      "heapUsedBytes": 58163948,
      "domNodes": -268,
      "jsHeapTotalBytes": 3485696,
      "scriptDurationMs": 66.475,
      "eventListeners": -195,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 1991.5050000000178,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.0809999999999995,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 410.325,
      "heapDeltaBytes": 7692972,
      "heapUsedBytes": 61204436,
      "domNodes": -265,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 78.911,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2091.038000000026,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 16.241,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 878.405,
      "heapDeltaBytes": -5730176,
      "heapUsedBytes": 79646240,
      "domNodes": -271,
      "jsHeapTotalBytes": 1855488,
      "scriptDurationMs": 311.751,
      "eventListeners": -195,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2052.186000000006,
      "styleRecalcs": 67,
      "styleRecalcDurationMs": 16.151000000000007,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 854.2660000000002,
      "heapDeltaBytes": 27601252,
      "heapUsedBytes": 80754748,
      "domNodes": -275,
      "jsHeapTotalBytes": 204800,
      "scriptDurationMs": 298.631,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3025.156999999979,
      "styleRecalcs": 64,
      "styleRecalcDurationMs": 16.091,
      "layouts": 60,
      "layoutDurationMs": 7.313999999999999,
      "taskDurationMs": 1005.0669999999999,
      "heapDeltaBytes": 12208872,
      "heapUsedBytes": 66664944,
      "domNodes": -277,
      "jsHeapTotalBytes": 2621440,
      "scriptDurationMs": 390.777,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3085.2689999999825,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 21.369,
      "layouts": 60,
      "layoutDurationMs": 7.783000000000001,
      "taskDurationMs": 1205.0140000000001,
      "heapDeltaBytes": -4037480,
      "heapUsedBytes": 70123164,
      "domNodes": 18,
      "jsHeapTotalBytes": 11153408,
      "scriptDurationMs": 450.316,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 2020.1220000000149,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 5.974000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 429.739,
      "heapDeltaBytes": -8454152,
      "heapUsedBytes": 60336280,
      "domNodes": -263,
      "jsHeapTotalBytes": -1056768,
      "scriptDurationMs": 70.619,
      "eventListeners": -197,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 1997.8590000000054,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.2639999999999985,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 509.63599999999997,
      "heapDeltaBytes": 10507660,
      "heapUsedBytes": 71839472,
      "domNodes": 16,
      "jsHeapTotalBytes": 5767168,
      "scriptDurationMs": 83.72099999999999,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 511.1220000000003,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 10.944,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 329.05400000000003,
      "heapDeltaBytes": -4041928,
      "heapUsedBytes": 48001268,
      "domNodes": -272,
      "jsHeapTotalBytes": 389120,
      "scriptDurationMs": 99.312,
      "eventListeners": -193,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999727
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 525.6739999999809,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 10.000000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 340.49800000000005,
      "heapDeltaBytes": -22484044,
      "heapUsedBytes": 47223524,
      "domNodes": -273,
      "jsHeapTotalBytes": 6443008,
      "scriptDurationMs": 98.785,
      "eventListeners": -193,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000273
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2021.3110000000256,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 7.485000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 306.19399999999996,
      "heapDeltaBytes": 18962396,
      "heapUsedBytes": 71469412,
      "domNodes": -288,
      "jsHeapTotalBytes": 15593472,
      "scriptDurationMs": 10.766000000000002,
      "eventListeners": -197,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2020.4919999999902,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 8.267,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 375.003,
      "heapDeltaBytes": -8240240,
      "heapUsedBytes": 61539084,
      "domNodes": -275,
      "jsHeapTotalBytes": 19812352,
      "scriptDurationMs": 15.709000000000001,
      "eventListeners": -195,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1706.388000000004,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 29.792,
      "layouts": 16,
      "layoutDurationMs": 3.4479999999999995,
      "taskDurationMs": 514.5269999999999,
      "heapDeltaBytes": 15952748,
      "heapUsedBytes": 74412376,
      "domNodes": 66,
      "jsHeapTotalBytes": 25427968,
      "scriptDurationMs": 68.246,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1687.702999999999,
      "styleRecalcs": 80,
      "styleRecalcDurationMs": 30.93,
      "layouts": 16,
      "layoutDurationMs": 3.382,
      "taskDurationMs": 548.509,
      "heapDeltaBytes": -10488796,
      "heapUsedBytes": 48415052,
      "domNodes": 66,
      "jsHeapTotalBytes": 26214400,
      "scriptDurationMs": 77.067,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-transition-enter",
      "durationMs": 883.2219999999893,
      "styleRecalcs": 20,
      "styleRecalcDurationMs": 25.941000000000006,
      "layouts": 16,
      "layoutDurationMs": 11.935999999999998,
      "taskDurationMs": 646.6750000000001,
      "heapDeltaBytes": 29901104,
      "heapUsedBytes": 101568076,
      "domNodes": 13673,
      "jsHeapTotalBytes": 16777216,
      "scriptDurationMs": 24.98999999999999,
      "eventListeners": 2531,
      "totalBlockingTimeMs": 101,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8144.889999999976,
      "styleRecalcs": 252,
      "styleRecalcDurationMs": 53.34199999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 2970.834,
      "heapDeltaBytes": -15248276,
      "heapUsedBytes": 68698752,
      "domNodes": -260,
      "jsHeapTotalBytes": 7622656,
      "scriptDurationMs": 957.6510000000001,
      "eventListeners": -179,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333338,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8141.12700000004,
      "styleRecalcs": 251,
      "styleRecalcDurationMs": 57.407,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 3373.15,
      "heapDeltaBytes": 21433144,
      "heapUsedBytes": 93236732,
      "domNodes": 20,
      "jsHeapTotalBytes": 19456000,
      "scriptDurationMs": 1091.532,
      "eventListeners": 20,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 11064.068000000021,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 11039.738,
      "heapDeltaBytes": -43633364,
      "heapUsedBytes": 172756252,
      "domNodes": -8314,
      "jsHeapTotalBytes": 21069824,
      "scriptDurationMs": 437.604,
      "eventListeners": -16372,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 11298.16500000004,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 11288.889000000001,
      "heapDeltaBytes": -40446724,
      "heapUsedBytes": 166351244,
      "domNodes": -3301,
      "jsHeapTotalBytes": 9961472,
      "scriptDurationMs": 452.475,
      "eventListeners": -16368,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.669999999999952,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 13453.634999999962,
      "styleRecalcs": 67,
      "styleRecalcDurationMs": 17.60299999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 13434.118999999999,
      "heapDeltaBytes": -26898728,
      "heapUsedBytes": 179029084,
      "domNodes": -3300,
      "jsHeapTotalBytes": 6729728,
      "scriptDurationMs": 707.097,
      "eventListeners": -16376,
      "totalBlockingTimeMs": 2,
      "frameDurationMs": 17.220000000000073,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 14019.350000000031,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 17.830000000000013,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 13996.855,
      "heapDeltaBytes": -34657680,
      "heapUsedBytes": 165893456,
      "domNodes": -3298,
      "jsHeapTotalBytes": 16748544,
      "scriptDurationMs": 681.75,
      "eventListeners": -16374,
      "totalBlockingTimeMs": 25,
      "frameDurationMs": 17.223333333333358,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "workflow-execution",
      "durationMs": 467.04399999998714,
      "styleRecalcs": 18,
      "styleRecalcDurationMs": 21.113,
      "layouts": 4,
      "layoutDurationMs": 0.9990000000000001,
      "taskDurationMs": 179.15300000000002,
      "heapDeltaBytes": -22015560,
      "heapUsedBytes": 48709072,
      "domNodes": -161,
      "jsHeapTotalBytes": 4345856,
      "scriptDurationMs": 14.997000000000002,
      "eventListeners": -130,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.799999999999727
    },
    {
      "name": "workflow-execution",
      "durationMs": 477.94999999996435,
      "styleRecalcs": 17,
      "styleRecalcDurationMs": 23.531000000000002,
      "layouts": 4,
      "layoutDurationMs": 1.0299999999999998,
      "taskDurationMs": 198.14499999999998,
      "heapDeltaBytes": -22297716,
      "heapUsedBytes": 48466616,
      "domNodes": -177,
      "jsHeapTotalBytes": 4870144,
      "scriptDurationMs": 18.683999999999997,
      "eventListeners": -134,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66666666666665,
      "p95FrameDurationMs": 16.700000000000273
    }
  ]
}

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a manifest-driven custom-node browser regression suite, with new fixtures, validation helpers, browser specs, workflow assets, scripts, CI, and docs. It also updates ComfyPage.createUser to treat duplicate usernames as a successful no-op.

Changes

Custom-node regression suite

Layer / File(s) Summary
Manifest schema and pack metadata
browser_tests/fixtures/customNode/manifest.ts, browser_tests/fixtures/data/customNodeManifest.json, browser_tests/tests/customNodes/manifest.pure.spec.ts, browser_tests/tests/customNodes/ADDING_PACKS.md, browser_tests/tests/customNodes/README.md, browser_tests/README.md
Defines the manifest entry shape, validates and loads the manifest, adds pack descriptors, and covers manifest structure, renderer-pass selection, and pack onboarding docs.
Run validation and desktop target
browser_tests/fixtures/customNode/runResult.ts, browser_tests/fixtures/customNode/objectInfoValidator.ts, browser_tests/fixtures/customNode/ComfyTarget.ts, browser_tests/fixtures/ComfyPage.ts, browser_tests/tests/customNodes/objectInfoValidator.pure.spec.ts, browser_tests/tests/customNodes/runResult.pure.spec.ts
Adds prompt-event and run-result types, object-info validation helpers, the local desktop target for fetching node defs and running workflows, and duplicate-username handling in createUser.
Type pairing and connectivity tests
browser_tests/fixtures/customNode/typePairing.ts, browser_tests/tests/customNodes/typePairing.pure.spec.ts, browser_tests/tests/customNodes/connectivity.spec.ts
Implements normalized node-slot pairing utilities and browser coverage for compatibility rules, deterministic pairing, round-trip persistence, and drag-based wiring across both renderers.
Workflow fixtures, utilities, and browser specs
browser_tests/assets/customNodes/*.json, browser_tests/fixtures/utils/consoleErrorCollector.ts, browser_tests/fixtures/utils/errorSurfaces.ts, browser_tests/fixtures/utils/customNodeSuite.ts, browser_tests/tests/customNodes/coreSmoke.spec.ts, browser_tests/tests/customNodes/customNode.regression.spec.ts
Adds workflow graph fixtures, console/error-surface helpers, suite settings and dialog dismissal, plus the smoke and regression specs for load and execution coverage.
Scripts, Chrome config, CI, and docs
package.json, playwright.chrome.config.ts, .github/workflows/ci-tests-custom-nodes.yaml
Adds custom-node Playwright scripts, a Chrome-only config, and a CI workflow that installs manifest-declared packs and rejects skipped tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested labels: size:XXL

Suggested reviewers: jtydhr88, christian-byrne, dante01yoon

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
End-To-End Regression Coverage For Fixes ✅ Passed No src/ or packages/ frontend code changes; the PR adds browser_tests coverage and CI/docs, so the fail conditions aren’t met.
Adr Compliance For Entity/Litegraph Changes ✅ Passed No changed files touch src/lib/litegraph, src/ecs, or graph-entity code; the PR is browser-test/docs/CI scaffolding, so ADR 0003/0008 checks don’t apply.
Title check ✅ Passed The title is concise and directly summarizes the new custom-node E2E regression suite.
Description check ✅ Passed The description covers the required summary, changes, and review focus, and is sufficiently complete despite minor template deviations.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nathaniel/custom-node-e2e-suite

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@CodeJuggernaut CodeJuggernaut changed the title test: manifest-driven custom-node E2E regression suite test: custom-node E2E regression suite Jul 2, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
browser_tests/README.md (1)

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

Avoid hardcoding the suite total.

16 passed will drift as soon as the manifest grows, which is the documented extension path for this suite. Describe success in terms of all manifest packs passing instead.

♻️ Suggested wording
-| `pnpm test:custom-nodes` | whole suite headless - the pass/fail gate (expect `16 passed`, zero skips) |
+| `pnpm test:custom-nodes` | whole suite headless - the pass/fail gate (all manifest packs pass, zero unexpected skips) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@browser_tests/README.md` at line 22, The browser_tests README currently
hardcodes the suite result as 16 passed, which will become stale as the manifest
grows. Update the success wording in the README to describe the outcome
generically in terms of all manifest packs passing, and adjust the related
browser_tests documentation text so it no longer depends on a fixed total.
♻️ Duplicate comments (1)
browser_tests/tests/customNodes/spikeDesktop.spec.ts (1)

28-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate of customNode.regression.spec.ts beforeEach.

Same concern as flagged in customNode.regression.spec.ts (lines 42-47): this block is copy-pasted verbatim. Consolidating into a shared helper avoids the two specs diverging silently.

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

In `@browser_tests/tests/customNodes/spikeDesktop.spec.ts` around lines 28 - 33,
The test setup in test.beforeEach is duplicated verbatim from
customNode.regression.spec.ts, so consolidate the repeated
template-workflows-content hide logic into a shared helper and call it from both
specs. Move the shared steps (getting the
getByTestId('template-workflows-content'), waiting for visible, pressing Escape,
and waiting for hidden) into one reusable function or fixture, then update
spikeDesktop.spec.ts and the regression spec to use that helper so they stay in
sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@browser_tests/fixtures/customNode/manifest.ts`:
- Line 4: The manifest lookup in the customNode manifest module still relies on
the process working directory, so update the path handling around MANIFEST_PATH
and the readFileSync(resolve(...)) usage to resolve relative to import.meta.url
instead. Adjust the manifest loading logic in manifest.ts so it builds an
absolute path from this module’s location, keeping the existing MANIFEST_PATH
constant but no longer depending on a repo-root relative string.

In `@browser_tests/fixtures/utils/errorSurfaces.ts`:
- Around line 9-10: The test selectors in errorSurfaces use raw string literals
for test ids, which should be centralized to avoid drift. Update the
errorOverlay and errorDialog locators to reference the shared TestIds constants
used elsewhere in ComfyPage.ts instead of hardcoded strings, keeping the
selector definitions aligned with the centralized test-id source.

In `@browser_tests/tests/customNodes/customNode.regression.spec.ts`:
- Around line 29-38: The test.use initialSettings block is duplicated across
customNode.regression.spec.ts and spikeDesktop.spec.ts, so centralize the shared
settings into a common constant such as customNodeSuiteSettings and reuse it in
both specs. Keep the existing values for Comfy.TutorialCompleted, Comfy.userId,
and Comfy.RightSidePanel.ShowErrorsTab, and update the relevant test.use calls
to reference the shared symbol so both suites stay in sync.
- Around line 42-47: The `beforeEach` in `customNode.regression.spec.ts`
duplicates the template-dismissal steps already used in `spikeDesktop.spec.ts`.
Extract this logic into a shared helper such as
`dismissTemplatesDialog(comfyPage)` in the same utilities area as
`errorSurfaces` and `collectConsoleErrors`, then replace both inline
`beforeEach` blocks with the helper call to keep the behavior centralized and
prevent drift.

In `@browser_tests/tests/customNodes/README.md`:
- Around line 63-68: The add-pack checklist in the customNodes README is missing
manifest fields that loadManifest() actually validates, so the documented
example would fail before tests run. Update the instructions around the
customNodeManifest entry and the run/load steps to include the required schema
fields repo, pin, workflow, requiresGpu, and requiresModels, and keep the
guidance aligned with the existing fixtures and assets referenced by
browser_tests/fixtures/data/customNodeManifest.json and
browser_tests/assets/customNodes/.

In `@browser_tests/tests/customNodes/spikeDesktop.spec.ts`:
- Around line 1-63: The test file name and its purpose are inconsistent: the
current spike-oriented name does not match the maintained smoke/regression suite
described by the `test.describe('smoke: core workflow')` block and the
`smokeWorkflow` setup. Rename the spec file to a stable, descriptive name such
as `coreSmoke.spec.ts` (or equivalent) so the filename matches the long-lived
test intent and is easy to discover alongside the `collectConsoleErrors` and
`errorSurfaces` checks.

---

Outside diff comments:
In `@browser_tests/README.md`:
- Line 22: The browser_tests README currently hardcodes the suite result as 16
passed, which will become stale as the manifest grows. Update the success
wording in the README to describe the outcome generically in terms of all
manifest packs passing, and adjust the related browser_tests documentation text
so it no longer depends on a fixed total.

---

Duplicate comments:
In `@browser_tests/tests/customNodes/spikeDesktop.spec.ts`:
- Around line 28-33: The test setup in test.beforeEach is duplicated verbatim
from customNode.regression.spec.ts, so consolidate the repeated
template-workflows-content hide logic into a shared helper and call it from both
specs. Move the shared steps (getting the
getByTestId('template-workflows-content'), waiting for visible, pressing Escape,
and waiting for hidden) into one reusable function or fixture, then update
spikeDesktop.spec.ts and the regression spec to use that helper so they stay in
sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ae0a1a1a-deae-4829-aa7b-479f132dcd81

📥 Commits

Reviewing files that changed from the base of the PR and between a6db1ab and 4fb282f.

📒 Files selected for processing (20)
  • browser_tests/README.md
  • browser_tests/assets/customNodes/core_smoke.json
  • browser_tests/assets/customNodes/impact_primitives_run.json
  • browser_tests/assets/customNodes/vhs_video_pipeline_run.json
  • browser_tests/fixtures/ComfyPage.ts
  • browser_tests/fixtures/customNode/ComfyTarget.ts
  • browser_tests/fixtures/customNode/manifest.ts
  • browser_tests/fixtures/customNode/objectInfoValidator.ts
  • browser_tests/fixtures/customNode/runResult.ts
  • browser_tests/fixtures/data/customNodeManifest.json
  • browser_tests/fixtures/utils/consoleErrorCollector.ts
  • browser_tests/fixtures/utils/errorSurfaces.ts
  • browser_tests/tests/customNodes/README.md
  • browser_tests/tests/customNodes/customNode.regression.spec.ts
  • browser_tests/tests/customNodes/manifest.pure.spec.ts
  • browser_tests/tests/customNodes/objectInfoValidator.pure.spec.ts
  • browser_tests/tests/customNodes/runResult.pure.spec.ts
  • browser_tests/tests/customNodes/spikeDesktop.spec.ts
  • package.json
  • playwright.chrome.config.ts

Comment thread browser_tests/fixtures/customNode/manifest.ts Outdated
Comment thread browser_tests/fixtures/utils/errorSurfaces.ts Outdated
Comment thread browser_tests/tests/customNodes/customNode.regression.spec.ts Outdated
Comment thread browser_tests/tests/customNodes/customNode.regression.spec.ts
Comment thread browser_tests/tests/customNodes/README.md Outdated
Comment thread browser_tests/tests/customNodes/coreSmoke.spec.ts
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

@@            Coverage Diff             @@
##             main   #13389      +/-   ##
==========================================
- Coverage   78.17%   78.13%   -0.04%     
==========================================
  Files        1648     1651       +3     
  Lines       95561    96845    +1284     
  Branches    33460    32627     -833     
==========================================
+ Hits        74704    75670     +966     
- Misses      20182    20464     +282     
- Partials      675      711      +36     
Flag Coverage Δ
unit 65.74% <ø> (+0.13%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 151 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.

@CodeJuggernaut CodeJuggernaut force-pushed the nathaniel/custom-node-e2e-suite branch from f8d2ee6 to addcb60 Compare July 2, 2026 19:08

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
browser_tests/fixtures/customNode/manifest.ts (1)

25-34: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate requiresGpu here as well.

The regression spec already branches on entry.requiresGpu, but assertEntry() does not require that field. If it is missing or misspelled in the manifest, GPU-only packs can be treated as CPU-runnable and the run tier will misclassify them.

🔧 Minimal fix
-    ['pack', 'workflow', 'expectedNodes', 'tiers', 'requiresModels'] as const
+    ['pack', 'workflow', 'expectedNodes', 'tiers', 'requiresModels', 'requiresGpu'] as const
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@browser_tests/fixtures/customNode/manifest.ts` around lines 25 - 34,
`assertEntry()` in `manifest.ts` is missing validation for `requiresGpu`, so
manifest entries can omit or misspell it and still pass. Update the
missing-field checks in `assertEntry(entry, index)` to require `requiresGpu`
alongside the other mandatory properties, using the same null/undefined
validation pattern and error reporting already used for `pack`, `workflow`,
`expectedNodes`, `tiers`, and `requiresModels`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@browser_tests/tests/customNodes/connectivity.spec.ts`:
- Around line 292-294: The `outDot.dragTo` call in `connectivity.spec.ts` is
tripping the `no-force-option` lint rule because it uses `{ force: true }`; keep
the forced drag since the z-999 overlay blocks pointer events, but add a local
lint suppression on that specific call using the existing overlay justification
so the `connectivity.spec` test remains valid and CI passes.

---

Outside diff comments:
In `@browser_tests/fixtures/customNode/manifest.ts`:
- Around line 25-34: `assertEntry()` in `manifest.ts` is missing validation for
`requiresGpu`, so manifest entries can omit or misspell it and still pass.
Update the missing-field checks in `assertEntry(entry, index)` to require
`requiresGpu` alongside the other mandatory properties, using the same
null/undefined validation pattern and error reporting already used for `pack`,
`workflow`, `expectedNodes`, `tiers`, and `requiresModels`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: faeeeab0-0c5a-45ba-9239-21ef7d2f47b2

📥 Commits

Reviewing files that changed from the base of the PR and between 8f567e8 and c9aa1fc.

📒 Files selected for processing (7)
  • browser_tests/fixtures/customNode/manifest.ts
  • browser_tests/fixtures/customNode/typePairing.ts
  • browser_tests/fixtures/data/customNodeManifest.json
  • browser_tests/tests/customNodes/README.md
  • browser_tests/tests/customNodes/connectivity.spec.ts
  • browser_tests/tests/customNodes/typePairing.pure.spec.ts
  • package.json

Comment thread browser_tests/tests/customNodes/connectivity.spec.ts Outdated
@CodeJuggernaut CodeJuggernaut force-pushed the nathaniel/custom-node-e2e-suite branch from bbb543c to 7a92da1 Compare July 2, 2026 19:13
A type-pairing generator indexes /object_info producers and consumers and
plans one representative typed edge per slot, excluding wildcard slots
(isValidConnection short-circuits on * before the real type compare, so a
wildcard link proves reachability, not interop). The breadth sweep connects
every planned edge through the real validator in-page and requires each link
to survive serialize/configure and appear in graphToPrompt output; verified
up front that graphToPrompt emits links even when other required inputs
dangle. A curated subset is dragged slot-dot to slot-dot under both
renderers, addressed by data-slot-key so shared labels cannot misfire.
Orphan types are reported, never failed; connect vetoes must match a
committed allow-list. Manifest packs opt in via a connectivity tier that
needs no extra assets.
@CodeJuggernaut CodeJuggernaut force-pushed the nathaniel/custom-node-e2e-suite branch from 7a92da1 to 8b81a4f Compare July 2, 2026 19:14
A permanent self-check feeds the shared pair executor a type-incompatible
pair and a fabricated slot name and requires CONNECT_REJECTED and
SLOT_CONTRACT_MISMATCH back, so a green sweep can never come from a
classifier that lost the ability to fail. The breadth test asserts every
connectivity-tier pack contributes pairs, guarding pack attribution. The
drag tier's widget-primitive exclusion is removed: widget-backed inputs
render real slot dots under Vue Nodes (verified empirically), so every pack
now gets an in-pack drag in both renderers, asserted present.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
browser_tests/fixtures/customNode/manifest.ts (1)

25-34: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Tighten manifest shape checks.

assertEntry only rejects nullish values, so malformed tiers/expectedNodes values can still pass and later skew the connectivity filter and length-based checks. Validate the actual array types here (and keep timeoutMs finite/positive) so bad manifests fail fast.

Based on source_other: browser_tests/tests/customNodes/connectivity.spec.ts and browser_tests/tests/customNodes/customNode.regression.spec.ts consume these fields as arrays.

🛠️ Proposed fix
 function assertEntry(entry: CustomNodeManifestEntry, index: number): void {
-  const missing: string[] = (
-    ['pack', 'workflow', 'expectedNodes', 'tiers', 'requiresModels'] as const
-  ).filter((key) => entry[key] == null)
-  if (typeof entry.timeoutMs !== 'number') missing.push('timeoutMs')
+  const missing: string[] = []
+  if (typeof entry.pack !== 'string' || entry.pack.length === 0) missing.push('pack')
+  if (typeof entry.workflow !== 'string' || entry.workflow.length === 0) missing.push('workflow')
+  if (!Array.isArray(entry.expectedNodes)) missing.push('expectedNodes')
+  if (!Array.isArray(entry.tiers)) missing.push('tiers')
+  if (!Array.isArray(entry.requiresModels)) missing.push('requiresModels')
+  if (!Number.isFinite(entry.timeoutMs) || entry.timeoutMs <= 0) missing.push('timeoutMs')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@browser_tests/fixtures/customNode/manifest.ts` around lines 25 - 34, The
manifest validation in assertEntry only checks for nullish values, so malformed
tiers and expectedNodes can still pass through and break later array-based
logic. Update assertEntry to verify those fields are actual arrays before
accepting the entry, and also tighten timeoutMs validation in the same function
to require a finite positive number. Keep the checks centralized in assertEntry
so bad custom node manifests fail fast before connectivity.spec and
customNode.regression.spec consume the data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@browser_tests/tests/customNodes/connectivity.spec.ts`:
- Line 94: The breadth sweep in connectivity.spec.ts starts a console error
collector via collectConsoleErrors(comfyPage.page), but the collected errors are
never asserted before stopping it. Update the breadth-sweep test around the
collectConsoleErrors usage and the final stop/teardown block to check
consoleErrors.errors the same way the fidelity test does, so any console errors
during the connect/serialize/prompt loop fail the test.
- Around line 59-61: The concrete() helper is duplicating the wildcard check
instead of reusing the shared predicate. Export isWildcard from typePairing.ts
and update concrete() in connectivity.spec.ts to use !isWildcard(slot.type) so
the wildcard logic stays centralized and cannot drift apart.
- Around line 217-230: The “in-pack” selection in the connectivity test is using
the full node pool, so it can return a compatible pair from any pack instead of
the current one. Update the logic around connectivityEntries to build the
candidate set from only the nodes in entry.pack before calling planPairs, rather
than filtering after the fact. Use the planPairs result and the
pair.producer.pack/pair.consumer.pack check only as a sanity guard, and keep
dragEdges populated from same-pack matches.

---

Outside diff comments:
In `@browser_tests/fixtures/customNode/manifest.ts`:
- Around line 25-34: The manifest validation in assertEntry only checks for
nullish values, so malformed tiers and expectedNodes can still pass through and
break later array-based logic. Update assertEntry to verify those fields are
actual arrays before accepting the entry, and also tighten timeoutMs validation
in the same function to require a finite positive number. Keep the checks
centralized in assertEntry so bad custom node manifests fail fast before
connectivity.spec and customNode.regression.spec consume the data.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fc2ff995-7c7c-4e41-b687-b2109380e87f

📥 Commits

Reviewing files that changed from the base of the PR and between addcb60 and 8b81a4f.

📒 Files selected for processing (7)
  • browser_tests/fixtures/customNode/manifest.ts
  • browser_tests/fixtures/customNode/typePairing.ts
  • browser_tests/fixtures/data/customNodeManifest.json
  • browser_tests/tests/customNodes/README.md
  • browser_tests/tests/customNodes/connectivity.spec.ts
  • browser_tests/tests/customNodes/typePairing.pure.spec.ts
  • package.json

Comment thread browser_tests/tests/customNodes/connectivity.spec.ts
Comment thread browser_tests/tests/customNodes/connectivity.spec.ts
Comment thread browser_tests/tests/customNodes/connectivity.spec.ts
Resolve the manifest path from import.meta.url so tests are cwd-independent,
and validate requiresGpu at manifest load. Reuse the centralized TestIds for
the error overlay, error dialog, and templates dialog selectors. Extract the
shared suite settings and templates-dialog dismissal into
fixtures/utils/customNodeSuite so the three specs cannot drift. Rename
spikeDesktop.spec.ts to coreSmoke.spec.ts to match its maintained purpose,
document the full manifest schema in the README, and describe the gate
outcome without a hardcoded test count.
…checks

The breadth sweep now fails on any console error captured during the
connect/serialize/prompt loop, matching the fidelity test. The wildcard
predicate is exported from typePairing and reused instead of re-derived.
assertEntry validates real shapes (non-empty pack/expectedNodes/tiers,
arrays, boolean requiresGpu, finite positive timeoutMs); workflow stays
allowed as an empty string until a pack gains a run-tier fixture.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 2, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 2, 2026
knip flags exported types with no external consumers; CustomNodeTier,
ObjectInfoNode, NormalizedSlot, and SlotRef are referenced only within
their own modules.
CodeJuggernaut and others added 16 commits July 7, 2026 12:32
…t fidelity, widget round-trip, combo pairing

Executes ~340 more nodes and hardens every tier's assertions:

- CHAINABLE verdict: required sockets with a model-free producer
  (EmptyImage, EmptyLatentImage, SolidMask, Primitive*, EmptyAudio) are
  synthesized and wired automatically; NEEDS_WIRES now means only truly
  unproducible types (MODEL, SEGS, CONDITIONING...)
- auto-run asserts data flow: every PreviewAny sink must emit a ui
  payload (NO_OUTPUT class); OUTPUT_NODE targets stay event-covered
- Vue mount asserts DOM widget/slot counts (missing fails, extras and
  in-row control_after_generate tolerated)
- save/reload is now two passes: pristine (reload must never shrink a
  node or change a value - the "widgets disappear" bug class) and
  set-and-stick (every plain widget holds a programmatic non-default
  write and it survives reload where topology is stable)
- connectivity pairs COMBO slots on exact option-vocabulary match
  (+~120 pairs); mismatched vocabularies stay excluded by design
- harness invariants: node ids never reused within a page (the
  widgetValueStore keys state by node id and survives graph.clear(), so
  a reused id inherits stale widget values - core bug, reported
  separately), and run events are filtered by prompt id + graph node id
  membership so late websocket events or flap-retry double-queues can
  never pin one node's failure on the next
- new mechanism ledgers: WIDGET_SET_ALLOWLIST, ROUNDTRIP_VALUE_ALLOWLIST,
  MOUNT_WIDGET_ALLOWLIST, all stale-guarded; AUTO_RUN_EXCLUDE gains the
  observed offenders (rembg pip-install-at-execute, empty-find infinite
  loop, from_pretrained downloads, minutes-long per-pixel loops)
- manifest baselines reconciled against three observation runs; stale
  entries removed, real failures (missing optional deps, degenerate
  synthesized inputs, CUDA-only recorders) baselined

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…renderers; reconcile Linux CI flips

Mount fidelity now has a renderer-independent bar: under BOTH the
LiteGraph and Vue passes, every created instance must materialize
everything its def declares - each non-socketless input exists as a
widget or a socket (autogrow templates count via their dot-qualified
expansion slots, e.g. variables.a/variables.b), and every declared
output exists. The Vue pass keeps its extra layer: the DOM must render
at least the instance's widget and slot counts. Verified against all
823 nodes under both renderers; the only def-shape special case found
was the core autogrow container semantics.

Also reconciles the first Linux CI run of the chain-builder tier:
- environment flips move to AUTO_RUN_EXCLUDE with mechanisms and leave
  the baseline: Image Analyze, Text Parse A1111 Embeddings (fail macOS,
  clean Linux), Image Crop Face (clean macOS, AttributeError Linux),
  ImageReceiver (av decode error macOS, clean Linux)
- run-to-run flip-floppers excluded: ImpactRemoteInt,
  ImpactSchedulerAdapter, ImpactQueueTriggerCountdown (queue-hook JS
  transient refusals), LoadText|pysssss (state-dependent file combo)

Local verification: allNodes 21/21 twice consecutively, plus lint,
format:check, knip, and both typechecks clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-side queue throws; scope decisions written in-code

Coverage-gap audit after the mount-fidelity miss (assertions silently
narrower than their claim). Fixes:

- save/reload now runs under BOTH renderers with staged evaluates and
  frame yields, so Vue component mount/configure effects actually flush
  before each serialize - the one renderer-dependent value path a
  LiteGraph-only pass could not see. Console errors are now collected
  during the tier too (configure-time pack JS noise was uncovered).
- queuePrompt is wrapped in-page: pack JS that THROWS mid-graphToPrompt
  (VHS applyToGraph crashed CI's whole VHS tier) now classifies as
  VALIDATION_FAIL carrying the exception text, so the offender
  self-identifies instead of aborting the tier. VHS_SelectLatest
  excluded with that mechanism: its applyToGraph assumes downstream
  inputs have widgets and hard-crashes when its output feeds a pure
  socket while the input dir has matching files (upstream-report
  candidate).
- pack-owned-value nodes (ROUNDTRIP_VALUE_ALLOWLIST) no longer receive
  set-and-stick probe writes - writing `_cn` markers into editor JSON
  widgets just made pack JS choke on our own probes.
- deliberate scopes are now stated where the assertion lives: auto-run
  runs single-renderer because execution is a backend contract and
  values flow through the same store in both renderers; it deliberately
  skips the zero-visible-errors check because it provokes expected
  failures; the connectivity breadth sweep is renderer-independent with
  the curated drag test covering both renderers; combo vocabulary
  matching is deliberately order-sensitive (option order defines the
  default).

Local verification: full customNodes suite 67/67 under CI parity, plus
lint, format:check, knip, and both typechecks clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pair combos with unknown options

A wired combo input bypasses its own widget, so menu order and the
options[0] default are not part of the wire contract - membership is
(backend validation checks value-in-options). Vocabulary fingerprints
are now order-insensitive (sorted, element-wise canonicalized). In the
current corpus this changes zero pairs (measured: no same-set,
different-order combos exist across the 7 packs); the rule is now
correct for packs where they do.

Auditing that change surfaced a real hole: the frontend's transformed
defs present some combos as the literal string COMBO with options in
the opts object. The old fingerprint hashed all of those identically,
silently cross-pairing dropdowns with no vocabulary evidence - exactly
the checkpoint-into-scheduler class the combo rule exists to exclude.
Normalization now pulls V2-form options, and a combo with no known
option list is excluded from pairing instead of blind-matched. Plan
moves 5,058 -> 5,030 pairs; the 28 removed were vocabulary-blind.

Also from CI: MiDaS Mask Image excluded (torch.hub download inside
execute hung the Linux runner; runs clean only where the hub cache is
warm) and ImageTransformKJ ledgered (pack JS initializes its
fill-options JSON widget on configure).

Local verification: full customNodes suite 68/68 twice, lint, format,
knip, and both typechecks clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ceInput beats every form, census-derived fixtures

Systematic audit for siblings of the two combo bug classes (ungrounded
contract, shape blindness), driven by a live shape census of the exact
getNodeDefs object the suite consumes:

- classifyInput now handles the V2 schema form (string 'COMBO' with
  options in the opts object; 495 such inputs exist in the transformed
  defs): options present = widget, empty or remote/lazy = NEEDS_MODELS.
  Real effect measured: 8 KJNodes nodes were silently misclassified
  NEEDS_WIRES and never executed - 5 now run clean, 3 correctly land in
  NEEDS_MODELS (remote combos).
- forceInput now beats every input form, list-form combos included (a
  census-found form the old branch order classified as widget; today's
  4 instances are optional or non-manifest, so this is protection, not
  a behavior change).
- pure-spec fixtures for both parsers now include every census form,
  copied from real census examples (V2 options, V2 empty, V2 remote,
  forceInput-on-combo, cross-form vocabulary pairing) so fixtures can
  no longer self-confirm the parser's assumptions.
- ADDING_CUSTOM_NODES.md gains the evidence rules: independent-oracle
  grounding for semantic claims, shape-census-driven parsing with
  exclude-with-record on unknown shapes, and verify-against-the-source-
  the-code-consumes.

defaultInput checked against frontend source: deprecated and ignored
(nodeDefStore warning only) - deliberately not handled.

Local verification: full customNodes suite 72/72 under CI parity; lint,
format:check, knip, and both typechecks clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he incident behind every invariant

The suite had run/onboarding docs but nothing describing the SYSTEM:
what the pieces are, how node definitions flow through the planners and
classifiers, how the execution harness attributes outcomes, and why each
non-obvious rule exists. This adds the missing third doc with four
Mermaid box-line diagrams (system overview, def data flow, run pipeline,
CI pipeline), the tier-by-renderer coverage matrix, the full ledger
table with the two-way baseline semantics, and the hard-won invariants
each tied to the incident that forced it (widgetValueStore id bleed,
event cross-attribution, pack JS queue-hook crashes, Vue effect timing,
queue-jam tripwire). Scope contract is stated up front: compatibility
and regression gate, not a behavior certifier.

Every path, symbol, and number cross-checked against the tree before
commit. README and ADDING_CUSTOM_NODES now cross-reference it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s, gotchas reference

Architecture documentation for the custom-node regression suite,
written as design views rather than an implementation dump:

- eight responsibility-level views: system context, building blocks,
  the node-definition pipeline, the execution flow, the persistence
  check, event attribution, the evidence model, and the CI deployment
  view; every diagram box names a responsibility or concept, arrows
  carry meaning, and decision points read in plain English
- a one-minute What/Why/How opening with the three explicit non-goals
  (output semantics, frontend-virtual nodes, hour-scale soak) and a
  clearly labeled scale snapshot so instance numbers never read as
  properties of the design
- a 12-row design-decisions table with honest trade-offs (why a real
  browser at all, why the backend serves the built frontend, one
  worker, disabled execution cache, pinned pack versions, one-row
  extensibility, per-tier renderer policy, mechanism-carrying
  exceptions, the two-way baseline, batch+bisect, and the scope line),
  plus the curated-workflow fixture named as the deliberate extension
  seam
- a 14-item gotchas reference, each entry in symptom / root cause /
  defense / which-team-concern-it-answers form, with named nodes kept
  only as worked examples of their class
- one implementation map section where architecture names meet code
  symbols, covering every building block including the orchestrator
  and the evidence ledgers
- the workflow's rotted sharding comment fixed (suite duration and
  the real shard trigger)

Grounded on the C4 model's published guidance, reviewed by an
independent architect pass (two view-coherence gaps found and fixed)
after three earlier Opus review passes on content accuracy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reader feedback on the rendered views: D2's service-to-tier arrows
crossed the whole diagram with ambiguous fan-ins (three unattributed
"pass/fail + exceptions" curves), and D3's corpus box mixed the
two-dialects annotation into a flow node right where three arrows fan
out, reading as if the dialects explained the fan-out.

Fixes, using the rules that make the CI view work: one direction of
flow per diagram, no many-to-many edges (the service-to-tier matrix is
now a table, which is what a matrix is), and annotations live in prose
rather than inside flow boxes. D2 is now a straight
manifest -> orchestrator -> tiers -> evidence -> verdict pipeline with
a three-row shared-services table; D3 moves the dialect fact into the
normalize step, labels the fan-out "derives", and adds one sentence
mapping each derived plan to its consuming tier.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…m D1-D4

Reader feedback on the rendered views: manual line breaks inside boxes
force Mermaid to render narrow, tall boxes with heavy wrapping, so the
diagrams cost too much scrolling. Mermaid sizes a box to its longest
line, so the fix is one or two long lines per box with elaboration in
the prose below the diagram. Applied to D1 (context), D2 (pipeline),
D3 (definition pipeline), and D4 (execution flow); D7 and D8 stay as
approved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e batching box

Headings were double-numbered ("2. D1 - system context") with an
internal diagram-numbering scheme that means nothing to a reader.
Sections are already numbered: headings now just name the view, and
every cross-reference points at a section. Also replaced "queue cost
is amortized" with plain English: one submission carries many nodes
instead of paying the round-trip per node.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The frontend box was a dead end: the suite drove it but nothing flowed
back, so the verdicts arrow to the team looked sourceless. Added the
return edge (observations back: what mounted, what persisted, what
executed, every error) and reworded the team edge so verdicts are
visibly the synthesis of those observations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… width

The building-blocks pipeline rendered as a tall narrow strip for two
mermaid reasons: labels auto-wrap at the ~200px default regardless of
line length, and a subgraph's declared direction is ignored once it has
external edges, so the tier row silently stacked vertically. The view
is now a left-to-right pipeline with the tier group in the middle, and
the wrap-width directive makes boxes wide instead of tall here and in
the definition-pipeline and execution-flow views.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ctured per review

- context diagram flows one way: driver -> frontend -> verdict synthesis -> team
- execution flow: classification fans out to its three verdicts; runnable paths
  converge on batching, blocked routes straight to reconciliation
- persistence check: sequence diagram replaced with a linear pipeline (one
  actor issuing commands is a procedure, not a message exchange)
- building blocks: tiers fan 2x2 inside the horizontal pipeline
- tripwire step + small recovers? diamond instead of one giant diamond

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…; name the map's ordering principle

Dagre's crossing minimizer ignores edge declaration order, so the fix is
node declaration order. Also states explicitly that the map is ordered by
zoom, not page order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed stack claims

Implements the cold insider review's full finding set plus pipeline
verification fixes:
- title gains 'regression', matching every sibling doc
- wiring tier renderer coverage told truthfully (breadth sweep one,
  curated drags both); decision 7 enumerates all renderer surfaces
- section 5 gains the manifest tiers vocabulary bridge (run and
  connectivity gate; load and io currently gate nothing)
- renderers named once (LiteGraph / Vue Nodes 2.0); opener deduplicated
  against section 1; implementation map matches the real manifest schema
- section 13 names Playwright, bundled Chromium, GitHub Actions, with a
  caveated runtime ballpark; gotcha receipts carry only verifiable claims
- sections 10 and 13 diagrams conform to the doc's diagram grammar
- ADDING_CUSTOM_NODES tiers gloss and manifest.ts workflow comment
  aligned with the same gating reality

Reviewed via ninja pipeline: 4-hat CORE panel (2 passes), senior QA gate
(2 rounds), gated review (Primary, Double Checker, Ultimate Skeptic).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iminating self-checks

- positive prompt-id attribution: capture the /prompt response id as the
  primary event filter (seen-set + graph membership stay as depth); a new
  attribution self-check injects a foreign-prompt terminal error mid-run
  and proves it cannot fail the run
- console collection now includes pageerror (uncaught exceptions and
  rejections), with a collector self-check as positive control; surfaces a
  real Custom-Scripts betterCombos typeof-null bug, ledgered with mechanism
- connectivity allowlists are two-way stale-guarded: every entry must be
  observed failing in its recorded way, all stale keys reported per run
- manifest pins are required full 40-hex SHAs (CUSTOM_NODES_ALLOW_UNPINNED=1
  admits only empty pins, reserved for the planned pack-HEAD canary); pack
  must be a plain path segment; contract pinned by pure specs
- CI installs each pack under custom_nodes/<pack> with charset and pin
  gates before cloning (attribution keys on the install dirname)
- allNodes renderer loops honor rendererPassesFor (vueNodesCompatible)
- curated T1 asserts every display sink emitted a ui payload; console
  sinks documented as excluded (no ui payload by design)
- the always()-wrapper suggestion was rejected on sibling evidence:
  ci-tests-unit.yaml gates its required check with a changes job and
  job-level if, and no repo workflow uses a wrapper

Reviewed via ninja pipeline: 4-hat CORE panel (2 passes), senior QA gate
(2 rounds, discrimination proven by falsification), gated review (Primary,
Double Checker, Ultimate Skeptic - 15-entry evidence ledger, all PROVEN).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Jul 8, 2026
CodeJuggernaut and others added 9 commits July 8, 2026 11:43
…uth pass from round-2 review

- Extract the pack console-error allowlist into a shared fixture
  (consoleErrorLedger.ts); the curated T1 run now collects console and
  page errors across load+run and asserts them through the ledger. The
  filter is pinned by a discriminating pure spec (pattern match,
  cross-pack ownership, unknown-pack fail-open).
- T1 asserts every expectedNodes type is actually present in the curated
  workflow before running it, killing the vacuous-green path where a
  drifted fixture shrank the executed-set check to an empty id list.
- typePairing records unrecognizable slot specs (unknownSlots on the
  node, unknownShapes on the plan) instead of silently dropping them;
  connectivity logs the list; pure tests pin the input/output drop paths
  and the socketless boundary.
- Add test:custom-nodes:ci, the gate-equivalent run against the
  backend-served built frontend; README re-scopes test:custom-nodes as
  the dev-server loop that is NOT the gate, and scopes the
  zero-visible-errors invariant to the tiers that hold it.
- ARCHITECTURE truth pass: event attribution leads with the positive
  prompt-id capture; section 10 grades ledger guards in three strengths;
  the decentralized parser story (declaredShape, classifyInput,
  normalizer) is stated consistently in section 4, gotcha G5, and the
  legend; ADDING_CUSTOM_NODES points the console ledger at its new home.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… onboarding mirrors the scoped invariant

Round-3 review follow-ups: the definition-pipeline diagram no longer
implies a centralized normalizer (live census -> wiring slot normalizer /
execution classifier / mount declared-shape parser, matching section 4),
and ADDING_CUSTOM_NODES scopes the zero-visible-errors claim to the tiers
that assert it, same wording as the README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…exercise found

- Wiring drop resolution: the curated drag test only targeted first-slot
  inputs, so a slot hit-test regression that falls back to the first
  compatible input went undetected. Add a second-slot anchor
  (EmptyImage.IMAGE -> ImageBatch.image2) that only links if the drop
  resolves the exact slot; proven by breaking getNodeInputOnPos.
- Curated-run failure naming: a backend validation rejection answers
  /prompt with node_errors but app.queuePrompt swallows it, so a
  VALIDATION_FAIL reported {}. Capture and flatten the node_errors
  (summarizePromptError, typed off apiSchema PromptResponse) into the
  result's clientError and surface it in the T1 message, so a red names
  the node and input. Exported with a pure unit test since the happy path
  never runs it.
- Console-error window: document (README + ARCHITECTURE section 10) that
  the ledger collects per-tier, so boot-time pack console noise before the
  first tier action is out of scope by design, backstopped by the startup
  zero-visible-errors check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rrelate to real regressions)

The correlation matrix + throwaway-PR plan that proves the suite catches
every failure mode ARCHITECTURE.md claims: one deliberate break per
surface, each citing the real historical regression it recreates (Linear
Custom Node Bugs issues + FE PR #12279) and the exact CI red it produces.
Every "exact red" is captured from a real falsification run, not a
prediction. Renames the earlier "kill-test" work to the falsification
pass. States the honest local-full-run idempotency caveat (CI shards
per-pack; a single-backend serial run is not the oracle).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erson reference)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n-noise filter, slow-node budgets)

The suite runs all 7 packs' execution tiers against one shared backend
locally (CI shards one backend per pack). Serial execution created three
distinct cross-test contaminations that failed a different set of packs
each run; each is now fixed at its mechanism:

- Foreign execution noise: mount/persistence/wiring/T0/core-smoke tiers
  queue no prompts, yet caught a prior tier's async execution error
  (PromptExecutionError, a 400 on /api/prompt). isForeignExecutionNoise
  filters execution-domain console lines from the non-executing tiers
  only; the executing tiers still assert them. Same "not this test's
  evidence" principle as event attribution (ARCHITECTURE section 9).
- Queue contention: the auto-run queue-busy guard hard-failed when a prior
  pack's slow CPU execution was still draining. drainUntilIdle waits it
  out (interrupt + clear + poll, throw-on-error so a failed read counts as
  busy); only a genuinely wedged backend fails. runBatch's post-timeout
  drain grows from 5s to 90s for the same reason.
- Slow-under-load misread as a regression: the single-node disambiguation
  re-run gets 60s instead of the batch's 20s. A real hang still exceeds it.

Also excludes the CLIPSeg model loaders (essentials, WAS) - model-download
nodes, same non-interruptible class as the listed BLIP/SAM/MiDaS loaders.
Reviewed by four-hat CORE (ship it); the new predicate is unit-pinned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… to idle

Every test gets a fresh page but all tests share one backend, locally and
on CI alike (the CI job is deliberately unsharded). A test ending while
its prompt still executed left that work running, and the next test's
fresh page connected mid-execution and inherited its async errors or its
busy queue. Drain the backend to idle in an afterEach in all four
backend-running specs, while the finishing test's own page is still open
so late events land there: no test can affect the next.

The drain helper moves to the shared fixture util (drainBackendToIdle,
byte-identical body); the auto-run tier's queue guard and runBatch
post-timeout drain rewire to it with their explicit budgets. The hooks
use a 10s budget: a no-op when already idle, and a backend still busy
past it is wedged, which the auto-run tier's 150s guard surfaces with
the restart diagnostic.

DETECTION_PROOF.md's caveat is rewritten to match, and a false claim
that CI shards one backend per pack is corrected in every location
(code comments and doc): the CI job runs the whole suite against one
fresh backend on an unloaded runner, which is why executions stay
inside their budgets there.

Empirical: a full-suite run with the hook eliminated the cross-test
bleed class entirely (zero mount/save-reload/core-smoke console or
overlay failures, previously 3-5 per run).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nation audit

Every quoted red message re-verified against its original run log (10/10
match). Seven citation corrections from the audit: the iTools missing-
button tickets are Nodes 2.0 regressions and move to the v2 mount row
(the v1 row is now honestly class-only); the SAM3 hidden-values tickets
are removed (extras-exposed class, which the mount tier tolerates by
design, so citing them overclaimed coverage); the persistence row now
cites the verified defaultInput migration regression (widgets reverting
to socket-only on reload) that open PR #12279 fixes, instead of a live
widget-interaction ticket; the links-type and serialization rows drop
borrowed tickets and state their class plainly; the drag row's tickets
are labeled nearest-symptom family; the two expansion bullets now cite
the committed pure-spec catches instead of an unproven live-sweep catch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pack-mode breaks are delivered via manifest pin swaps (CI clones packs at
pins, so in-repo pack-file edits cannot reach it); corpus-derived red
messages promise the tier and failure class, not byte-identical offender
text; remaining citations and mechanism descriptions tightened to what the
code and captured runs actually show.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants