Skip to content

test: custom-node E2E regression suite - #13389

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

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

Conversation

@CodeJuggernaut

@CodeJuggernaut CodeJuggernaut commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Manifest-driven Playwright conformance suite for community custom-node packs, plus the two CI gates that run it: custom-nodes-e2e-core against a local pinned ComfyUI with every manifest pack pip-installed, and custom-nodes-e2e-cloud against Comfy Cloud through a vite preview of the cloud dist. There is no per-pack test code. A pack is one manifest row; the suite re-reads that pack's real node list from /object_info on every run, derives what each node should be able to do, and verifies it in a real browser against a real backend with the pack's own frontend scripts loaded. Every exception is a reviewed ledger entry carrying its mechanism, and execution results are reconciled in both directions against a committed baseline, so the gate can neither hide a regression nor accumulate dead exemptions. Nothing is ever skipped: a skip fails the job.

Architecture, scope, local runs, pack onboarding, and the detection proof live in the source-of-truth design doc: Custom Nodes E2E Regression Suite - Technical Design Doc. docs/custom-node-regression-suite.md is the repo stub that points at it.

Core covers 6 pinned packs on a local backend; cloud covers 87 deployed packs. Scale snapshot from the doc, printed by a run and moving with the manifest and pins: about 800 registered nodes, about 5,000 planned wiring checks, about 440 nodes executing clean per run.

Coverage tiers, each one falsified against the core gate by applying its break in isolation and confirming the tier catches and names it. The proof is #13534 (DO NOT MERGE): one deliberate break per tier, with the CI runs showing each break caught and named; the per-row matrix is also summarized in the doc. All 15 legs verified red-when-broken:

Tier What it proves Core state Cloud state
S1 Every registered node mounts completely on the canvas renderer: the instance materializes every input and output its definition declares PASS PARTIAL
S2 Same mount completeness under the DOM renderer (Vue Nodes 2.0), including the rendered widget and slot counts PASS PARTIAL
S3 Save/reload persistence: no widget silently disappears, no serialized value drifts, and a non-default write sticks across a second reload PASS FIX PUSHED, CI RUNNING
S4 Slots wire type-correctly through the real connection validator, one representative typed edge per slot across the whole installed corpus PASS PASS
S5 Drop resolution: curated pointer drags land on the exact target slot, under both renderers PASS PASS
S6 Frontend prompt serialization: the curated workflow's widget values reach the backend intact and pass validation PASS BLOCKED BY S9
S7 Zero visible errors, including an extension hook that throws during graph load PASS RED
S8 Console and uncaught page-error ledger across the curated run, including pack JS that throws silently PASS BLOCKED BY S9
S9 Backend execution: every runnable node executes clean, classified and reconciled two ways against a committed known-failure baseline PASS FAIL, EXTERNAL BLOCKER
S10 Registration sentinels: a renamed or dropped node key trips the zero-skip gate PASS RED
S11 The pack's frontend JS actually loaded: every declared extension name is registered in the browser PASS RED, PACK-SCOPED
S12 Dynamic input autogrow driven by pack JS: connect grows a slot, disconnect removes the trailing empty, by drag and programmatically, both renderers PASS N/A WHILE ABSENT
S13 Interaction profiles: every node's instantiate, connect and disconnect shape deltas diffed against committed baselines PASS DEFERRED
S14 Layout geometry: node size, widget-row offsets and slot positions against committed baselines to 0.01px PASS DEFERRED
S15 Output regression: content hashes of the curated run's sink payloads PASS DEFERRED
S16 Screenshot tier BACKLOG BACKLOG

S1 through S12 run in both environments, so a frontend regression is caught wherever it lands. S13 through S15 are core-only today: all three compare against committed baselines, and the cloud baselines come from the two record workflows (geometry, output hashes), neither of which has been run to a commit yet. All three defers are deliberate and self-expiring: S13 and S14 wait until every other cloud tier is green, S15 until cloud output hashes are recorded, and each tier arms itself the moment its baselines land.

Current state. Core gate green as of 226b0b2; the commits since touch shared files, so a core re-run at head is required before merge. Cloud gate: the auth rework has landed, since testcloud no longer accepts a raw Firebase token, so the fixture seeds a Firebase session and mints a real workspace JWT; a follow-up boot-ordering fix keeps /api/features anonymous, so the router guard sees production's feature-flag payload instead of a premature unified_cloud_auth=true and its redirect/reload loop. The first full cloud run is in progress at time of writing.

Changes

  • What: suite specs in browser_tests/tests/customNodes/ and the harness in browser_tests/fixtures/customNode/; gating workflows ci-tests-custom-nodes.yaml (core) and ci-tests-custom-nodes-cloud.yaml (cloud); a nightly non-gating pack-drift canary; two baseline record workflows covering geometry and output hashes; scripts/gen-cloud-manifest.ts, which builds the cloud manifest from an /object_info probe snapshot joined to the deployed supported-nodes list, plus one small hand-maintained curated overlay that decides which rows carry the run tier.
  • Shared-fixture edits: ComfyPage gains cloud smoke-auth seeding and makes createUser idempotent on 400 Duplicate username.; litegraphUtils slot positions now go through canvasPosToClientPos, so mouse targeting survives pack JS that shifts the canvas off (0,0).
  • Breaking: none. No production behavior changes.
  • Dependencies: start-server-and-test and yaml, both devDependencies.

Review Focus

  • Both gates are meant to become required status checks. Path gating lives in a changes job rather than a trigger-level paths: filter, so a required check never wedges Pending on an unrelated PR, and fork PRs skip the job (the core gate's manifest install loop runs setup.py from cloned repos, and the cloud gate needs secrets forks never receive). Fork coverage stays with the main e2e shards.
  • Cloud sharding is not implemented and the cloud check must not be marked required before it is. The calibration run is runner-bound at roughly 20s per test on per-test app load, and sharding is blocked on the single smoke account: every shard would sign in as the same identity and share one backend queue. The prompt-scoped drain and an account pool unblock it.
  • The executed-set contract: only non-null executing events count as executed, so the core run tier requires a --cache-none backend.
  • The [cloud-auth-probe] console.warn instrumentation in src/stores/authStore.ts and src/platform/workspace/stores/workspaceAuthStore.ts is temporary debugging for the cloud auth boot order and comes out before merge. It is the only src/ change in this PR beyond a test file.

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

🔗 Website Preview

Website Preview: https://comfy-website-preview-pr-13389.vercel.app

This commit: https://website-frontend-nbgm3qito-comfyui.vercel.app

Last updated: 2026-08-13T00:23:56Z for 8ad9a47

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 08/13/2026, 12:24:04 AM UTC

Links

🎭 Playwright: 🕵🏻 0 passed, 0 failed

📊 Browser Reports

📦 Bundle: 8.72 MB gzip 🔴 +14 B

Details

Summary

  • Raw size: 36.8 MB baseline 36.8 MB — 🔴 +554 B
  • Gzip: 8.72 MB baseline 8.72 MB — 🔴 +14 B
  • Brotli: 6.08 MB baseline 6.08 MB — 🟢 -252 B
  • Bundles: 436 current • 436 baseline • 146 added / 146 removed

Category Glance
Vendor & Third-Party 🔴 +552 B (16.3 MB) · Data & Services 🔴 +35 B (3.51 MB) · Other 🟢 -33 B (14.2 MB) · Graph Workspace ⚪ 0 B (1.36 MB) · Panels & Settings ⚪ 0 B (565 kB) · Utilities & Hooks ⚪ 0 B (550 kB) · + 5 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-DP7ZdmLt.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.84 kB 🟢 -1.59 kB
assets/index-JNr0GCZE.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.83 kB 🔴 +1.6 kB

Status: 1 added / 1 removed

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

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-CMfUozul.js (new) 1.36 MB 🔴 +1.36 MB 🔴 +295 kB 🔴 +222 kB
assets/GraphView-Du7Yh3od.js (removed) 1.36 MB 🟢 -1.36 MB 🟢 -295 kB 🟢 -222 kB
assets/WidgetCompositor-BO6RADzm.js (removed) 8.17 kB 🟢 -8.17 kB 🟢 -2.76 kB 🟢 -2.45 kB
assets/WidgetCompositor-NC36c5Kl.js (new) 8.17 kB 🔴 +8.17 kB 🔴 +2.76 kB 🔴 +2.45 kB

Status: 2 added / 2 removed / 1 unchanged

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-B0Wq-RTf.js (removed) 25 kB 🟢 -25 kB 🟢 -6.24 kB 🟢 -5.52 kB
assets/CloudSurveyView-Cyhj93Il.js (new) 25 kB 🔴 +25 kB 🔴 +6.24 kB 🔴 +5.52 kB
assets/CloudLayoutView-BuKvjbnb.js (removed) 21.8 kB 🟢 -21.8 kB 🟢 -6.57 kB 🟢 -5.74 kB
assets/CloudLayoutView-BWW8gLvn.js (new) 21.8 kB 🔴 +21.8 kB 🔴 +6.57 kB 🔴 +5.74 kB
assets/UserCheckView-BFn1SgSo.js (new) 8.75 kB 🔴 +8.75 kB 🔴 +2.19 kB 🔴 +1.9 kB
assets/UserCheckView-qhMV8_hI.js (removed) 8.75 kB 🟢 -8.75 kB 🟢 -2.19 kB 🟢 -1.9 kB
assets/CloudLoginView-B86xTdO2.js (new) 8.74 kB 🔴 +8.74 kB 🔴 +2.55 kB 🔴 +2.25 kB
assets/CloudLoginView-D39rBKwx.js (removed) 8.74 kB 🟢 -8.74 kB 🟢 -2.55 kB 🟢 -2.25 kB
assets/useCloudAuthPage-bEEHvqbl.js (new) 7.08 kB 🔴 +7.08 kB 🔴 +2.51 kB 🔴 +2.2 kB
assets/useCloudAuthPage-o2rklmgB.js (removed) 7.08 kB 🟢 -7.08 kB 🟢 -2.51 kB 🟢 -2.19 kB
assets/CloudSignupView-7NIzQ64v.js (new) 6.56 kB 🔴 +6.56 kB 🔴 +2.18 kB 🔴 +1.91 kB
assets/CloudSignupView-C6D-2Tz2.js (removed) 6.56 kB 🟢 -6.56 kB 🟢 -2.17 kB 🟢 -1.91 kB
assets/WidgetTextPreview-BTgId3r0.js (removed) 6.07 kB 🟢 -6.07 kB 🟢 -2.13 kB 🟢 -1.89 kB
assets/WidgetTextPreview-De2Wup16.js (new) 6.07 kB 🔴 +6.07 kB 🔴 +2.13 kB 🔴 +1.89 kB
assets/CloudSubscriptionRedirectView-Ctm0pwbS.js (new) 6.05 kB 🔴 +6.05 kB 🔴 +2.25 kB 🔴 +1.96 kB
assets/CloudSubscriptionRedirectView-DsDerQJj.js (removed) 6.05 kB 🟢 -6.05 kB 🟢 -2.25 kB 🟢 -1.97 kB
assets/UserSelectView-BxuXgO0N.js (new) 5.49 kB 🔴 +5.49 kB 🔴 +1.96 kB 🔴 +1.71 kB
assets/UserSelectView-DSlzZNFP.js (removed) 5.49 kB 🟢 -5.49 kB 🟢 -1.96 kB 🟢 -1.71 kB
assets/CloudForgotPasswordView-Bd6Pfi0G.js (removed) 4.97 kB 🟢 -4.97 kB 🟢 -1.72 kB 🟢 -1.49 kB
assets/CloudForgotPasswordView-BQf8Kr0A.js (new) 4.97 kB 🔴 +4.97 kB 🔴 +1.72 kB 🔴 +1.49 kB
assets/CloudAuthTimeoutView-C8fucCW0.js (removed) 4.43 kB 🟢 -4.43 kB 🟢 -1.54 kB 🟢 -1.35 kB
assets/CloudAuthTimeoutView-NXt7Xr72.js (new) 4.43 kB 🔴 +4.43 kB 🔴 +1.54 kB 🔴 +1.34 kB
assets/OAuthLayoutView-BOkcKLg1.js (removed) 1.31 kB 🟢 -1.31 kB 🟢 -704 B 🟢 -587 B
assets/OAuthLayoutView-BuWCjM8D.js (new) 1.31 kB 🔴 +1.31 kB 🔴 +703 B 🔴 +587 B
assets/WidgetTextPreview-D_HEXGVX.js (removed) 131 B 🟢 -131 B 🟢 -100 B 🟢 -88 B
assets/WidgetTextPreview-DdHus3Z1.js (new) 131 B 🔴 +131 B 🔴 +100 B 🔴 +89 B

Status: 13 added / 13 removed / 4 unchanged

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-DbJAjLx1.js (removed) 49.4 kB 🟢 -49.4 kB 🟢 -9.94 kB 🟢 -8.82 kB
assets/KeybindingPanel-Dxp2VPMd.js (new) 49.4 kB 🔴 +49.4 kB 🔴 +9.94 kB 🔴 +8.82 kB
assets/SecretsPanel-_sbbyKCM.js (new) 33.7 kB 🔴 +33.7 kB 🔴 +7.87 kB 🔴 +6.9 kB
assets/SecretsPanel-Ba3wDB6L.js (removed) 33.7 kB 🟢 -33.7 kB 🟢 -7.87 kB 🟢 -6.9 kB
assets/CreditsPanel-BCh3gJX1.js (new) 11.5 kB 🔴 +11.5 kB 🔴 +3.2 kB 🔴 +2.81 kB
assets/CreditsPanel-D7XVTi1K.js (removed) 11.5 kB 🟢 -11.5 kB 🟢 -3.2 kB 🟢 -2.81 kB
assets/AboutPanel-D0dyAdpq.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.03 kB 🔴 +2.72 kB
assets/AboutPanel-D2TYHcCt.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.03 kB 🟢 -2.71 kB
assets/ExtensionPanel-Boegd5C3.js (removed) 9.19 kB 🟢 -9.19 kB 🟢 -2.51 kB 🟢 -2.22 kB
assets/ExtensionPanel-CuREFaHo.js (new) 9.19 kB 🔴 +9.19 kB 🔴 +2.51 kB 🔴 +2.22 kB
assets/ServerConfigPanel-CV-dU1mk.js (new) 6.09 kB 🔴 +6.09 kB 🔴 +1.94 kB 🔴 +1.72 kB
assets/ServerConfigPanel-Cz76CAaw.js (removed) 6.09 kB 🟢 -6.09 kB 🟢 -1.94 kB 🟢 -1.72 kB
assets/UserPanel-BDbT_HRX.js (new) 5.73 kB 🔴 +5.73 kB 🔴 +1.78 kB 🔴 +1.54 kB
assets/UserPanel-Cr3O79jR.js (removed) 5.73 kB 🟢 -5.73 kB 🟢 -1.78 kB 🟢 -1.54 kB
assets/refreshRemoteConfig-B_kK_h8_.js (new) 3.44 kB 🔴 +3.44 kB 🔴 +1.33 kB 🔴 +1.17 kB
assets/refreshRemoteConfig-BShEE_0_.js (removed) 3.44 kB 🟢 -3.44 kB 🟢 -1.33 kB 🟢 -1.17 kB
assets/cloudRemoteConfig-Bias60LG.js (removed) 951 B 🟢 -951 B 🟢 -517 B 🟢 -432 B
assets/cloudRemoteConfig-BLVfH30D.js (new) 951 B 🔴 +951 B 🔴 +517 B 🔴 +420 B
assets/refreshRemoteConfig-C95GXnoO.js (new) 110 B 🔴 +110 B 🔴 +89 B 🔴 +90 B
assets/refreshRemoteConfig-DmChBsqd.js (removed) 110 B 🟢 -110 B 🟢 -89 B 🟢 -84 B

Status: 10 added / 10 removed / 16 unchanged

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SignUpForm-DbKfzbgi.js (removed) 12.8 kB 🟢 -12.8 kB 🟢 -4.32 kB 🟢 -3.76 kB
assets/SignUpForm-W5bq59QI.js (new) 12.8 kB 🔴 +12.8 kB 🔴 +4.32 kB 🔴 +3.76 kB
assets/auth-Bcg4grwc.js (removed) 3.71 kB 🟢 -3.71 kB 🟢 -1.28 kB 🟢 -1.1 kB
assets/auth-BJueW7gY.js (new) 3.71 kB 🔴 +3.71 kB 🔴 +1.28 kB 🔴 +1.1 kB
assets/UpdatePasswordContent-DIj7FISW.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +840 B 🔴 +731 B
assets/UpdatePasswordContent-DsceGJQ7.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -841 B 🟢 -731 B
assets/authStore-BMSYM7V6.js (new) 128 B 🔴 +128 B 🔴 +107 B 🔴 +103 B
assets/authStore-hDBC5-3G.js (removed) 128 B 🟢 -128 B 🟢 -107 B 🟢 -102 B
assets/workspaceAuthStore-BrppTCUW.js (new) 108 B 🔴 +108 B 🔴 +99 B 🔴 +103 B
assets/workspaceAuthStore-CYWC0QpF.js (removed) 108 B 🟢 -108 B 🟢 -99 B 🟢 -105 B
assets/auth-CRf3MGhU.js (new) 105 B 🔴 +105 B 🔴 +96 B 🔴 +94 B
assets/auth-DgYiHGIc.js (removed) 105 B 🟢 -105 B 🟢 -96 B 🟢 -73 B

Status: 6 added / 6 removed / 4 unchanged

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

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyHubPublishDialog-BNZyht75.js (removed) 90.1 kB 🟢 -90.1 kB 🟢 -19.3 kB 🟢 -16.5 kB
assets/ComfyHubPublishDialog-BSYOGvBR.js (new) 90.1 kB 🔴 +90.1 kB 🔴 +19.3 kB 🔴 +16.5 kB
assets/useShareDialog-BYPdCX7-.js (new) 23.9 kB 🔴 +23.9 kB 🔴 +5.69 kB 🔴 +5.03 kB
assets/useShareDialog-DU_GXGuu.js (removed) 23.9 kB 🟢 -23.9 kB 🟢 -5.7 kB 🟢 -5.04 kB
assets/feedbackDialog-BWaJBTY1.js (removed) 4.45 kB 🟢 -4.45 kB 🟢 -1.87 kB 🟢 -1.59 kB
assets/feedbackDialog-COKpTn6r.js (new) 4.45 kB 🔴 +4.45 kB 🔴 +1.86 kB 🔴 +1.59 kB
assets/useRangeEditor-C2wmiQTl.js (new) 3.29 kB 🔴 +3.29 kB 🔴 +1.14 kB 🔴 +1.03 kB
assets/useRangeEditor-D3ZZW9n1.js (removed) 3.29 kB 🟢 -3.29 kB 🟢 -1.14 kB 🟢 -1.03 kB
assets/useLayerEditor-B1ikmRof.js (new) 1.01 kB 🔴 +1.01 kB 🔴 +491 B 🔴 +407 B
assets/useLayerEditor-Y4NoTlhM.js (removed) 1.01 kB 🟢 -1.01 kB 🟢 -491 B 🟢 -409 B
assets/ComfyHubPublishDialog-6KCkcxbb.js (new) 143 B 🔴 +143 B 🔴 +105 B 🔴 +91 B
assets/ComfyHubPublishDialog-CQ99eHhR.js (removed) 143 B 🟢 -143 B 🟢 -105 B 🟢 -89 B
assets/useSubscriptionDialog-BB4rls-U.js (removed) 108 B 🟢 -108 B 🟢 -102 B 🟢 -87 B
assets/useSubscriptionDialog-DEWJ4s2p.js (new) 108 B 🔴 +108 B 🔴 +102 B 🔴 +96 B

Status: 7 added / 7 removed / 1 unchanged

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-CJ-M_I-v.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.97 kB 🔴 +3.52 kB
assets/ComfyQueueButton-DF330qed.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.97 kB 🟢 -3.52 kB
assets/useTerminalTabs-CRw_G8I-.js (new) 11.8 kB 🔴 +11.8 kB 🔴 +3.69 kB 🔴 +3.27 kB
assets/useTerminalTabs-fKVOy202.js (removed) 11.8 kB 🟢 -11.8 kB 🟢 -3.69 kB 🟢 -3.27 kB
assets/InviteMembersForm-BG5XZswA.js (removed) 8.2 kB 🟢 -8.2 kB 🟢 -2.72 kB 🟢 -2.44 kB
assets/InviteMembersForm-DjeViUnl.js (new) 8.2 kB 🔴 +8.2 kB 🔴 +2.72 kB 🔴 +2.42 kB
assets/SubscribeButton-BnevnmLT.js (removed) 2.15 kB 🟢 -2.15 kB 🟢 -969 B 🟢 -848 B
assets/SubscribeButton-NdbnYJx1.js (new) 2.15 kB 🔴 +2.15 kB 🔴 +970 B 🔴 +846 B
assets/cloudFeedbackTopbarButton-BRSXYZot.js (new) 705 B 🔴 +705 B 🔴 +421 B 🔴 +360 B
assets/cloudFeedbackTopbarButton-DEXKs6bi.js (removed) 705 B 🟢 -705 B 🟢 -420 B 🟢 -362 B
assets/ComfyQueueButton-4K-u6BSB.js (new) 128 B 🔴 +128 B 🔴 +99 B 🔴 +90 B
assets/ComfyQueueButton-C5hWm_7S.js (removed) 128 B 🟢 -128 B 🟢 -99 B 🟢 -89 B

Status: 6 added / 6 removed / 8 unchanged

Data & Services — 3.51 MB (baseline 3.51 MB) • 🔴 +35 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/settingStore-DwEYeL6u.js (new) 3.23 MB 🔴 +3.23 MB 🔴 +749 kB 🔴 +563 kB
assets/settingStore-pmTIf51J.js (removed) 3.23 MB 🟢 -3.23 MB 🟢 -749 kB 🟢 -564 kB
assets/load3dService-DdVAfKqV.js (removed) 132 kB 🟢 -132 kB 🟢 -29.3 kB 🟢 -24.6 kB
assets/load3dService-f8binrRz.js (new) 132 kB 🔴 +132 kB 🔴 +29.3 kB 🔴 +24.6 kB
assets/api-BbdYEtrv.js (new) 98.3 kB 🔴 +98.3 kB 🔴 +27.2 kB 🔴 +23.4 kB
assets/api-DIdagwYh.js (removed) 98.3 kB 🟢 -98.3 kB 🟢 -27.2 kB 🟢 -23.3 kB
assets/workflowShareService-C48UUrrU.js (removed) 16.5 kB 🟢 -16.5 kB 🟢 -4.91 kB 🟢 -4.34 kB
assets/workflowShareService-COqtK63y.js (new) 16.5 kB 🔴 +16.5 kB 🔴 +4.91 kB 🔴 +4.35 kB
assets/keybindingService-3ZwO5Zhc.js (new) 6.89 kB 🔴 +6.89 kB 🔴 +1.73 kB 🔴 +1.5 kB
assets/keybindingService-DXUTPYQV.js (removed) 6.89 kB 🟢 -6.89 kB 🟢 -1.73 kB 🟢 -1.5 kB
assets/releaseStore-DXM3BPqd.js (new) 6.72 kB 🔴 +6.72 kB 🔴 +2.03 kB 🔴 +1.77 kB
assets/releaseStore-YZaffsBn.js (removed) 6.72 kB 🟢 -6.72 kB 🟢 -2.03 kB 🟢 -1.77 kB
assets/systemStatsStore-CMBridyO.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.74 kB 🟢 -1.47 kB
assets/systemStatsStore-CSaC2Zgg.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.74 kB 🔴 +1.47 kB
assets/userStore-Bv4PkTwV.js (removed) 2.38 kB 🟢 -2.38 kB 🟢 -897 B 🟢 -794 B
assets/userStore-DdKn9KED.js (new) 2.38 kB 🔴 +2.38 kB 🔴 +897 B 🔴 +795 B
assets/audioService-C45wo2kk.js (new) 1.71 kB 🔴 +1.71 kB 🔴 +829 B 🔴 +732 B
assets/audioService-uudXUw96.js (removed) 1.71 kB 🟢 -1.71 kB 🟢 -829 B 🟢 -730 B
assets/dialogService-BB6GpUR9.js (new) 98 B 🔴 +98 B 🔴 +97 B 🔴 +81 B
assets/dialogService-c0HAAM6O.js (removed) 98 B 🟢 -98 B 🟢 -97 B 🟢 -84 B
assets/releaseStore-DF-GtJaz.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -82 B
assets/releaseStore-Tjt9xNoV.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +92 B
assets/settingStore-DaNN4AV1.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +85 B
assets/settingStore-zMkiq7Jg.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -86 B
assets/assetsStore-CU54G__g.js (removed) 94 B 🟢 -94 B 🟢 -92 B 🟢 -84 B
assets/assetsStore-V7aLlIbB.js (new) 94 B 🔴 +94 B 🔴 +92 B 🔴 +83 B
assets/api-DfC4dw2f.js (removed) 62 B 🟢 -62 B 🟢 -74 B 🟢 -66 B
assets/api-DlwZrk7l.js (new) 62 B 🔴 +62 B 🔴 +74 B 🔴 +66 B

Status: 14 added / 14 removed / 3 unchanged

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

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useConflictDetection-CK4JdFDZ.js (removed) 236 kB 🟢 -236 kB 🟢 -53 kB 🟢 -43.1 kB
assets/useConflictDetection-CSdhubAL.js (new) 236 kB 🔴 +236 kB 🔴 +53 kB 🔴 +43.2 kB
assets/useLayerEditorSession-CUueeYNY.js (removed) 158 kB 🟢 -158 kB 🟢 -40.8 kB 🟢 -34.3 kB
assets/useLayerEditorSession-DBL4Kyyp.js (new) 158 kB 🔴 +158 kB 🔴 +40.8 kB 🔴 +34.3 kB
assets/useLoad3d-BwDxR4w4.js (removed) 25.8 kB 🟢 -25.8 kB 🟢 -5.8 kB 🟢 -5.14 kB
assets/useLoad3d-DQoDBrnQ.js (new) 25.8 kB 🔴 +25.8 kB 🔴 +5.8 kB 🔴 +5.15 kB
assets/useLoad3dViewer-BfQQFQLP.js (new) 21.2 kB 🔴 +21.2 kB 🔴 +4.98 kB 🔴 +4.36 kB
assets/useLoad3dViewer-rcnKk1eD.js (removed) 21.2 kB 🟢 -21.2 kB 🟢 -4.98 kB 🟢 -4.37 kB
assets/useImageCrop-B1qW0Rxd.js (new) 14.9 kB 🔴 +14.9 kB 🔴 +3.42 kB 🔴 +2.98 kB
assets/useImageCrop-DyQA_Wok.js (removed) 14.9 kB 🟢 -14.9 kB 🟢 -3.42 kB 🟢 -2.98 kB
assets/useDowngradeToPersonal-BArk7bX9.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -2.75 kB 🟢 -2.36 kB
assets/useDowngradeToPersonal-DB7zCwI-.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +2.75 kB 🔴 +2.36 kB
assets/useFeatureFlags-DYAeys0l.js (new) 6.99 kB 🔴 +6.99 kB 🔴 +2 kB 🔴 +1.7 kB
assets/useFeatureFlags-RfXYYNIs.js (removed) 6.99 kB 🟢 -6.99 kB 🟢 -2 kB 🟢 -1.7 kB
assets/useCompositorLayers-C0n55EGY.js (new) 2.94 kB 🔴 +2.94 kB 🔴 +899 B 🔴 +806 B
assets/useCompositorLayers-eGjWw6wB.js (removed) 2.94 kB 🟢 -2.94 kB 🟢 -898 B 🟢 -805 B
assets/assetPreviewUtil-Byq_xJa7.js (new) 2.35 kB 🔴 +2.35 kB 🔴 +966 B 🔴 +844 B
assets/assetPreviewUtil-CuluUxvr.js (removed) 2.35 kB 🟢 -2.35 kB 🟢 -968 B 🟢 -845 B
assets/useUpstreamValue-BzJ6OkYC.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +759 B 🔴 +683 B
assets/useUpstreamValue-CTWZhDn4.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -758 B 🟢 -672 B
assets/useWorkspaceTierLabel-BzDwAE6_.js (new) 1.93 kB 🔴 +1.93 kB 🔴 +812 B 🔴 +694 B
assets/useWorkspaceTierLabel-DlY90Wrn.js (removed) 1.93 kB 🟢 -1.93 kB 🟢 -812 B 🟢 -697 B
assets/subscriptionCheckoutUtil-CvmCZTN8.js (removed) 877 B 🟢 -877 B 🟢 -522 B 🟢 -434 B
assets/subscriptionCheckoutUtil-KQHMKcFG.js (new) 877 B 🔴 +877 B 🔴 +521 B 🔴 +434 B
assets/useSessionCookie-MRfy8KHQ.js (new) 652 B 🔴 +652 B 🔴 +337 B 🔴 +292 B
assets/useSessionCookie-NK_t1tI2.js (removed) 652 B 🟢 -652 B 🟢 -336 B 🟢 -293 B
assets/useLoad3d-BVnIM-yd.js (removed) 311 B 🟢 -311 B 🟢 -163 B 🟢 -148 B
assets/useLoad3d-BZrTuEkn.js (new) 311 B 🔴 +311 B 🔴 +162 B 🔴 +148 B
assets/useSessionCookie-BxfU2vsA.js (removed) 101 B 🟢 -101 B 🟢 -86 B 🟢 -83 B
assets/useSessionCookie-DiaqO6uV.js (new) 101 B 🔴 +101 B 🔴 +86 B 🔴 +81 B
assets/useFeatureFlags-B82gjKR_.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -82 B
assets/useFeatureFlags-DJRBSoUW.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +87 B
assets/useLoad3dViewer-DVYrgbmR.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +84 B
assets/useLoad3dViewer-zMzffSpA.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -81 B
assets/useCurrentUser-BY4P2qQu.js (removed) 94 B 🟢 -94 B 🟢 -95 B 🟢 -82 B
assets/useCurrentUser-DD9lOkwY.js (new) 94 B 🔴 +94 B 🔴 +95 B 🔴 +80 B

Status: 18 added / 18 removed / 20 unchanged

Vendor & Third-Party — 16.3 MB (baseline 16.3 MB) • 🔴 +552 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-axios-CyO1Ni1Z.js (new) 120 kB 🔴 +120 kB 🔴 +32 kB 🔴 +27.4 kB
assets/vendor-axios-I_gyl9iu.js (removed) 119 kB 🟢 -119 kB 🟢 -32 kB 🟢 -27.4 kB

Status: 1 added / 1 removed / 16 unchanged

Other — 14.2 MB (baseline 14.2 MB) • 🟢 -33 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-Bn20eXtD.js (removed) 115 kB 🟢 -115 kB 🟢 -29.7 kB 🟢 -25.1 kB
assets/core-C4buf4cZ.js (new) 115 kB 🔴 +115 kB 🔴 +29.7 kB 🔴 +25.1 kB
assets/WidgetSelect-DXJEHc_k.js (new) 88.8 kB 🔴 +88.8 kB 🔴 +20.1 kB 🔴 +17.2 kB
assets/WidgetSelect-O4c-FgWC.js (removed) 88.8 kB 🟢 -88.8 kB 🟢 -20.1 kB 🟢 -17.2 kB
assets/SubscriptionPanelContentWorkspace-Bw0iLN9B.js (removed) 80 kB 🟢 -80 kB 🟢 -15.8 kB 🟢 -13.6 kB
assets/SubscriptionPanelContentWorkspace-nYzlcMl8.js (new) 80 kB 🔴 +80 kB 🔴 +15.8 kB 🔴 +13.6 kB
assets/Load3D-BEP53aPm.js (new) 71.3 kB 🔴 +71.3 kB 🔴 +11.7 kB 🔴 +9.97 kB
assets/Load3D-BHPzyRgQ.js (removed) 71.3 kB 🟢 -71.3 kB 🟢 -11.7 kB 🟢 -9.98 kB
assets/WidgetVideoEdit-C_Y8ikH2.js (new) 67.5 kB 🔴 +67.5 kB 🔴 +15.7 kB 🔴 +13.9 kB
assets/WidgetVideoEdit-KSt3WGa9.js (removed) 67.5 kB 🟢 -67.5 kB 🟢 -15.7 kB 🟢 -13.9 kB
assets/SubscriptionTransitionPreviewWorkspace-CVAbAwUK.js (removed) 66.4 kB 🟢 -66.4 kB 🟢 -13.4 kB 🟢 -11.7 kB
assets/SubscriptionTransitionPreviewWorkspace-CZHOGRES.js (new) 66.4 kB 🔴 +66.4 kB 🔴 +13.4 kB 🔴 +11.7 kB
assets/WorkspaceSettingsPanelContent-CvUGmvfm.js (removed) 58.2 kB 🟢 -58.2 kB 🟢 -12.4 kB 🟢 -10.8 kB
assets/WorkspaceSettingsPanelContent-V_D8YbGQ.js (new) 58.2 kB 🔴 +58.2 kB 🔴 +12.4 kB 🔴 +10.8 kB
assets/Preview3d-C-lLG_xz.js (removed) 50.9 kB 🟢 -50.9 kB 🟢 -8.32 kB 🟢 -7.25 kB
assets/Preview3d-CBha46bs.js (new) 50.9 kB 🔴 +50.9 kB 🔴 +8.32 kB 🔴 +7.25 kB
assets/main-YU24xlO0.js (removed) 45.1 kB 🟢 -45.1 kB 🟢 -13.1 kB 🟢 -11.4 kB
assets/main-B6bbrAVI.js (new) 45.1 kB 🔴 +45.1 kB 🔴 +13.1 kB 🔴 +11.4 kB
assets/SubscriptionRequiredDialogContentUnified-BI16vkPb.js (new) 42.6 kB 🔴 +42.6 kB 🔴 +9.36 kB 🔴 +8.16 kB
assets/SubscriptionRequiredDialogContentUnified-DGgvt6IO.js (removed) 42.6 kB 🟢 -42.6 kB 🟢 -9.36 kB 🟢 -8.16 kB
assets/LayerEditorContent-CxjoYpK2.js (removed) 42.5 kB 🟢 -42.5 kB 🟢 -9.62 kB 🟢 -8.42 kB
assets/LayerEditorContent-D4f-O8vE.js (new) 42.5 kB 🔴 +42.5 kB 🔴 +9.62 kB 🔴 +8.43 kB
assets/WidgetBoundingBoxes-BFG1laQw.js (removed) 33.7 kB 🟢 -33.7 kB 🟢 -9.16 kB 🟢 -8.12 kB
assets/WidgetBoundingBoxes-DAVSElEl.js (new) 33.7 kB 🔴 +33.7 kB 🔴 +9.16 kB 🔴 +8.12 kB
assets/WidgetPainter-BQBBWWfa.js (removed) 32.6 kB 🟢 -32.6 kB 🟢 -7.88 kB 🟢 -6.97 kB
assets/WidgetPainter-DnKpW224.js (new) 32.6 kB 🔴 +32.6 kB 🔴 +7.88 kB 🔴 +6.97 kB
assets/Load3dViewerContent-DoeVhZY6.js (removed) 30.8 kB 🟢 -30.8 kB 🟢 -6.29 kB 🟢 -5.45 kB
assets/Load3dViewerContent-qKBpW6sx.js (new) 30.8 kB 🔴 +30.8 kB 🔴 +6.29 kB 🔴 +5.45 kB
assets/SubscriptionRequiredDialogContent-CTe4YkCh.js (new) 26.9 kB 🔴 +26.9 kB 🔴 +6.36 kB 🔴 +5.61 kB
assets/SubscriptionRequiredDialogContent-DV9v6Bhb.js (removed) 26.9 kB 🟢 -26.9 kB 🟢 -6.36 kB 🟢 -5.6 kB
assets/SubscriptionRequiredDialogContentWorkspace-CBoiv4ek.js (removed) 25.1 kB 🟢 -25.1 kB 🟢 -5.79 kB 🟢 -5.11 kB
assets/SubscriptionRequiredDialogContentWorkspace-jvwBJwEq.js (new) 25.1 kB 🔴 +25.1 kB 🔴 +5.79 kB 🔴 +5.1 kB
assets/CreditsTile-BjpfrJ95.js (removed) 24.9 kB 🟢 -24.9 kB 🟢 -6.65 kB 🟢 -5.84 kB
assets/CreditsTile-gO6vMefF.js (new) 24.9 kB 🔴 +24.9 kB 🔴 +6.65 kB 🔴 +5.84 kB
assets/load3d-BGQEvOaT.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.41 kB 🟢 -4.67 kB
assets/load3d-BX6bIn6_.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.4 kB 🔴 +4.67 kB
assets/CurrentUserPopoverWorkspace-BWXm8FxG.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +4.85 kB 🔴 +4.32 kB
assets/CurrentUserPopoverWorkspace-BZGGTDuG.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -4.84 kB 🟢 -4.31 kB
assets/SignInContent-CBeabgZT.js (new) 20.2 kB 🔴 +20.2 kB 🔴 +5.09 kB 🔴 +4.46 kB
assets/SignInContent-D5DbxBrz.js (removed) 20.2 kB 🟢 -20.2 kB 🟢 -5.09 kB 🟢 -4.45 kB
assets/WidgetRecordAudio-BX7vvGto.js (removed) 16.6 kB 🟢 -16.6 kB 🟢 -4.6 kB 🟢 -4.1 kB
assets/WidgetRecordAudio-D98BEH44.js (new) 16.6 kB 🔴 +16.6 kB 🔴 +4.6 kB 🔴 +4.1 kB
assets/WidgetInputNumber-CKW-jrYZ.js (new) 13.9 kB 🔴 +13.9 kB 🔴 +3.63 kB 🔴 +3.21 kB
assets/WidgetInputNumber-DbWKrzS0.js (removed) 13.9 kB 🟢 -13.9 kB 🟢 -3.63 kB 🟢 -3.21 kB
assets/WidgetRange-B1S8PG1a.js (new) 13.7 kB 🔴 +13.7 kB 🔴 +3.55 kB 🔴 +3.13 kB
assets/WidgetRange-CcV0WH2S.js (removed) 13.7 kB 🟢 -13.7 kB 🟢 -3.55 kB 🟢 -3.13 kB
assets/WaveAudioPlayer-BLfTCzx4.js (new) 12.8 kB 🔴 +12.8 kB 🔴 +3.46 kB 🔴 +3.04 kB
assets/WaveAudioPlayer-DM94kGJs.js (removed) 12.8 kB 🟢 -12.8 kB 🟢 -3.46 kB 🟢 -3.05 kB
assets/WidgetCurve-DjKB1juj.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.47 kB 🟢 -3.14 kB
assets/WidgetCurve-DWQEboY_.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.48 kB 🔴 +3.16 kB
assets/TeamWorkspacesDialogContent-CTjWKesn.js (removed) 10.3 kB 🟢 -10.3 kB 🟢 -2.97 kB 🟢 -2.63 kB
assets/TeamWorkspacesDialogContent-DYJsCvOJ.js (new) 10.3 kB 🔴 +10.3 kB 🔴 +2.97 kB 🔴 +2.63 kB
assets/onboardingCloudRoutes-C0-fPJq2.js (removed) 9.33 kB 🟢 -9.33 kB 🟢 -2.83 kB 🟢 -2.43 kB
assets/onboardingCloudRoutes-C9px2kbV.js (new) 9.33 kB 🔴 +9.33 kB 🔴 +2.83 kB 🔴 +2.42 kB
assets/Load3DConfiguration-B9mJzkqt.js (removed) 8.91 kB 🟢 -8.91 kB 🟢 -2.61 kB 🟢 -2.29 kB
assets/Load3DConfiguration-BNg8YN5R.js (new) 8.91 kB 🔴 +8.91 kB 🔴 +2.61 kB 🔴 +2.3 kB
assets/WidgetImageCrop-DDTdfJEE.js (new) 8.49 kB 🔴 +8.49 kB 🔴 +2.66 kB 🔴 +2.34 kB
assets/WidgetImageCrop-dERZBIj-.js (removed) 8.49 kB 🟢 -8.49 kB 🟢 -2.65 kB 🟢 -2.37 kB
assets/SetMemberCreditLimitDialogContent-Ce8iP6HA.js (removed) 8.47 kB 🟢 -8.47 kB 🟢 -2.34 kB 🟢 -2.04 kB
assets/SetMemberCreditLimitDialogContent-ekVarZ3y.js (new) 8.47 kB 🔴 +8.47 kB 🔴 +2.34 kB 🔴 +2.05 kB
assets/nodeTemplates-BdI7pfYJ.js (new) 8.32 kB 🔴 +8.32 kB 🔴 +2.85 kB 🔴 +2.5 kB
assets/nodeTemplates-CS-eSpb7.js (removed) 8.32 kB 🟢 -8.32 kB 🟢 -2.85 kB 🟢 -2.5 kB
assets/NightlySurveyController-bimiubP6.js (new) 7.5 kB 🔴 +7.5 kB 🔴 +2.56 kB 🔴 +2.24 kB
assets/NightlySurveyController-CkJl8mG8.js (removed) 7.5 kB 🟢 -7.5 kB 🟢 -2.56 kB 🟢 -2.25 kB
assets/CloudRunButtonWrapper-D8YRMit5.js (new) 6.84 kB 🔴 +6.84 kB 🔴 +2.18 kB 🔴 +1.91 kB
assets/CloudRunButtonWrapper-DOy9U6A7.js (removed) 6.84 kB 🟢 -6.84 kB 🟢 -2.18 kB 🟢 -1.91 kB
assets/WidgetWithControl-CHgejEg6.js (new) 6.41 kB 🔴 +6.41 kB 🔴 +2.61 kB 🔴 +2.3 kB
assets/WidgetWithControl-WoDhYMiN.js (removed) 6.41 kB 🟢 -6.41 kB 🟢 -2.61 kB 🟢 -2.32 kB
assets/missingModelMetadata-Bwj4DGLM.js (removed) 6.17 kB 🟢 -6.17 kB 🟢 -2.13 kB 🟢 -1.86 kB
assets/missingModelMetadata-YlFVToDY.js (new) 6.17 kB 🔴 +6.17 kB 🔴 +2.13 kB 🔴 +1.86 kB
assets/CancelSubscriptionDialogContent-B1vPSAXQ.js (removed) 5.97 kB 🟢 -5.97 kB 🟢 -1.98 kB 🟢 -1.75 kB
assets/CancelSubscriptionDialogContent-BbP1SQo0.js (new) 5.97 kB 🔴 +5.97 kB 🔴 +1.98 kB 🔴 +1.75 kB
assets/load3dPreviewExtensions-Cq-JzcSY.js (removed) 5.88 kB 🟢 -5.88 kB 🟢 -1.81 kB 🟢 -1.6 kB
assets/load3dPreviewExtensions-IDEpAM3K.js (new) 5.88 kB 🔴 +5.88 kB 🔴 +1.81 kB 🔴 +1.6 kB
assets/launchCancellationFlow-Bo-QNu-4.js (removed) 5.18 kB 🟢 -5.18 kB 🟢 -1.78 kB 🟢 -1.56 kB
assets/launchCancellationFlow-DYPguMFP.js (new) 5.18 kB 🔴 +5.18 kB 🔴 +1.78 kB 🔴 +1.56 kB
assets/CreateWorkspaceDialogContent-CG20G-46.js (removed) 5.12 kB 🟢 -5.12 kB 🟢 -1.79 kB 🟢 -1.55 kB
assets/CreateWorkspaceDialogContent-DhO-tIPm.js (new) 5.12 kB 🔴 +5.12 kB 🔴 +1.79 kB 🔴 +1.55 kB
assets/ChangeMemberRoleDialogContent-Co1CspGY.js (new) 4.97 kB 🔴 +4.97 kB 🔴 +1.64 kB 🔴 +1.42 kB
assets/ChangeMemberRoleDialogContent-D1wQZ9Ao.js (removed) 4.97 kB 🟢 -4.97 kB 🟢 -1.64 kB 🟢 -1.42 kB
assets/InviteMemberDialogContent-_0IWHKbd.js (removed) 4.96 kB 🟢 -4.96 kB 🟢 -1.65 kB 🟢 -1.44 kB
assets/InviteMemberDialogContent-C02FB1RC.js (new) 4.96 kB 🔴 +4.96 kB 🔴 +1.64 kB 🔴 +1.44 kB
assets/EditWorkspaceDialogContent-CgaVirBd.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.76 kB 🟢 -1.53 kB
assets/EditWorkspaceDialogContent-z3kHAKKl.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.76 kB 🔴 +1.53 kB
assets/WidgetTextarea-C8ae-1ph.js (new) 4.81 kB 🔴 +4.81 kB 🔴 +1.87 kB 🔴 +1.64 kB
assets/WidgetTextarea-mjjAV6LZ.js (removed) 4.81 kB 🟢 -4.81 kB 🟢 -1.87 kB 🟢 -1.64 kB
assets/saveMesh-7ZB1Nrxq.js (new) 4.76 kB 🔴 +4.76 kB 🔴 +1.52 kB 🔴 +1.34 kB
assets/saveMesh-EcaUt2Tm.js (removed) 4.76 kB 🟢 -4.76 kB 🟢 -1.52 kB 🟢 -1.34 kB
assets/WorkspacePanelContent-CEMA3Z-b.js (removed) 4.74 kB 🟢 -4.74 kB 🟢 -1.63 kB 🟢 -1.44 kB
assets/WorkspacePanelContent-Du85bOeW.js (new) 4.74 kB 🔴 +4.74 kB 🔴 +1.62 kB 🔴 +1.44 kB
assets/ValueControlPopover-2iJuunJQ.js (new) 4.49 kB 🔴 +4.49 kB 🔴 +1.55 kB 🔴 +1.38 kB
assets/ValueControlPopover-DeJuJsiG.js (removed) 4.49 kB 🟢 -4.49 kB 🟢 -1.55 kB 🟢 -1.38 kB
assets/DeleteWorkspaceDialogContent-BRMn6e1Q.js (removed) 3.84 kB 🟢 -3.84 kB 🟢 -1.44 kB 🟢 -1.24 kB
assets/DeleteWorkspaceDialogContent-D3OUwyNs.js (new) 3.84 kB 🔴 +3.84 kB 🔴 +1.44 kB 🔴 +1.24 kB
assets/RemoveMemberDialogContent-BXDncwET.js (removed) 3.76 kB 🟢 -3.76 kB 🟢 -1.38 kB 🟢 -1.2 kB
assets/RemoveMemberDialogContent-CWnq77Kx.js (new) 3.76 kB 🔴 +3.76 kB 🔴 +1.38 kB 🔴 +1.2 kB
assets/RevokeInviteDialogContent-48d3Ba4C.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.39 kB 🟢 -1.22 kB
assets/RevokeInviteDialogContent-Bvavr8qG.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.39 kB 🔴 +1.22 kB
assets/LeaveWorkspaceDialogContent-eodH3o5u.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.38 kB 🟢 -1.19 kB
assets/LeaveWorkspaceDialogContent-gq-bLm3C.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.38 kB 🔴 +1.19 kB
assets/InviteMemberUpsellDialogContent-CEAWIQvj.js (removed) 3.47 kB 🟢 -3.47 kB 🟢 -1.24 kB 🟢 -1.09 kB
assets/InviteMemberUpsellDialogContent-cNoqQjIu.js (new) 3.47 kB 🔴 +3.47 kB 🔴 +1.24 kB 🔴 +1.09 kB
assets/workspaceCheckoutTelemetry-BYKM9mew.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.52 kB 🟢 -1.32 kB
assets/workspaceCheckoutTelemetry-Cq34xavz.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.52 kB 🔴 +1.33 kB
assets/GlobalToast-Ctas9e1m.js (new) 3.25 kB 🔴 +3.25 kB 🔴 +1.3 kB 🔴 +1.11 kB
assets/GlobalToast-DPYkAnDt.js (removed) 3.25 kB 🟢 -3.25 kB 🟢 -1.3 kB 🟢 -1.11 kB
assets/Media3DTop-Du_zIYyA.js (new) 3.21 kB 🔴 +3.21 kB 🔴 +1.27 kB 🔴 +1.1 kB
assets/Media3DTop-DYaMVPKD.js (removed) 3.21 kB 🟢 -3.21 kB 🟢 -1.26 kB 🟢 -1.1 kB
assets/load3dAdvanced-Cmq0gqQi.js (new) 2.82 kB 🔴 +2.82 kB 🔴 +1.09 kB 🔴 +957 B
assets/load3dAdvanced-CTEcde2Y.js (removed) 2.82 kB 🟢 -2.82 kB 🟢 -1.1 kB 🟢 -956 B
assets/SubscribeToRun-BSPHLJgQ.js (new) 2.39 kB 🔴 +2.39 kB 🔴 +1.03 kB 🔴 +906 B
assets/SubscribeToRun-CcZe1keo.js (removed) 2.39 kB 🟢 -2.39 kB 🟢 -1.03 kB 🟢 -905 B
assets/MediaAudioTop-CnGoLcYy.js (removed) 1.62 kB 🟢 -1.62 kB 🟢 -808 B 🟢 -673 B
assets/MediaAudioTop-DS-MfsGS.js (new) 1.62 kB 🔴 +1.62 kB 🔴 +808 B 🔴 +671 B
assets/cloudSessionCookie-CRUVOVvR.js (removed) 933 B 🟢 -933 B 🟢 -433 B 🟢 -378 B
assets/cloudSessionCookie-DZBq0Gc4.js (new) 933 B 🔴 +933 B 🔴 +434 B 🔴 +377 B
assets/cloudBadges-B3qYWcY3.js (removed) 922 B 🟢 -922 B 🟢 -514 B 🟢 -435 B
assets/cloudBadges-fhqjYqCc.js (new) 922 B 🔴 +922 B 🔴 +515 B 🔴 +446 B
assets/Load3DAdvanced-B1OLGR5_.js (removed) 761 B 🟢 -761 B 🟢 -423 B 🟢 -359 B
assets/Load3DAdvanced-DNJALctx.js (new) 761 B 🔴 +761 B 🔴 +422 B 🔴 +359 B
assets/nightlyBadges-BB01aHOj.js (removed) 411 B 🟢 -411 B 🟢 -273 B 🟢 -231 B
assets/nightlyBadges-DKeNibEk.js (new) 411 B 🔴 +411 B 🔴 +272 B 🔴 +229 B
assets/Load3dViewerContent-6DOO1RPY.js (removed) 137 B 🟢 -137 B 🟢 -103 B 🟢 -104 B
assets/Load3dViewerContent-B68OUB37.js (new) 137 B 🔴 +137 B 🔴 +103 B 🔴 +94 B
assets/missingModelMetadata-B6VSof-l.js (new) 125 B 🔴 +125 B 🔴 +103 B 🔴 +103 B
assets/missingModelMetadata-C__0xRp5.js (removed) 125 B 🟢 -125 B 🟢 -103 B 🟢 -104 B
assets/Load3DAdvanced-BTdyBe8n.js (removed) 122 B 🟢 -122 B 🟢 -97 B 🟢 -84 B
assets/Load3DAdvanced-JLVFR3P3.js (new) 122 B 🔴 +122 B 🔴 +97 B 🔴 +90 B
assets/WidgetLegacy-C32WMYkY.js (removed) 117 B 🟢 -117 B 🟢 -106 B 🟢 -104 B
assets/WidgetLegacy-j6froH4R.js (new) 117 B 🔴 +117 B 🔴 +106 B 🔴 +103 B
assets/workflowDraftStoreV2-BmoKj2K_.js (new) 112 B 🔴 +112 B 🔴 +101 B 🔴 +107 B
assets/workflowDraftStoreV2-CTtBougi.js (removed) 112 B 🟢 -112 B 🟢 -101 B 🟢 -108 B
assets/Load3D-BvecJehb.js (new) 98 B 🔴 +98 B 🔴 +89 B 🔴 +82 B
assets/Load3D-CGaMhRNz.js (removed) 98 B 🟢 -98 B 🟢 -89 B 🟢 -82 B
assets/changeTracker-BqBh2t9e.js (removed) 91 B 🟢 -91 B 🟢 -93 B 🟢 -87 B
assets/changeTracker-BV2bpph8.js (new) 91 B 🔴 +91 B 🔴 +93 B 🔴 +84 B

Status: 68 added / 68 removed / 217 unchanged

⚡ Performance

⏳ Performance tests in progress…

@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.
Description check ✅ Passed The description includes the required Summary, Changes, and Review Focus sections and clearly explains the custom-node regression suite.
Title check ✅ Passed The title clearly and concisely identifies the main change: a custom-node end-to-end regression suite.
✨ 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   81.33%   81.34%           
=======================================
  Files        1873     1873           
  Lines      106533   106545   +12     
  Branches    33235    33236    +1     
=======================================
+ Hits        86653    86666   +13     
+ Misses      19536    19525   -11     
- Partials      344      354   +10     
Flag Coverage Δ
unit 72.54% <ø> (+<0.01%) ⬆️

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

Files with missing lines Coverage Δ
...try/providers/cloud/CustomerIoTelemetryProvider.ts 100.00% <ø> (+0.68%) ⬆️
...eNodes/widgets/composables/useImageUploadWidget.ts 91.93% <ø> (+0.55%) ⬆️

... and 9 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.
The Comfy.userId=default settings override broke every test on multi-user
backends (the repo's stated browser-test prerequisite): devtools
set_settings wrote to a user no session reads, so Comfy.TutorialCompleted
never landed, the templates dialog never opened, and the beforeEach wait
timed out - CI sessions even inherited leftover settings (a zh locale) from
earlier tests on the same worker user. Dropping the override lets the
fixture target the real per-worker user everywhere; the harness backend now
runs --multi-user like CI. Connectivity's per-pack guards and drag
derivation apply only to installed packs, so a backend without the manifest
packs reports the absence instead of hard-failing while the core sweep,
native drag, and self-checks still run.
CI caught what a pack-rich local backend masked: isValidConnection compares
only the string COMBO while every combo slot carries its own option set, so
the planner would wire a checkpoint dropdown into a scheduler dropdown and
call it proof, and combo outputs declare a non-string output_name whose
instance slot name never matches (DevToolsNodeWithOutputCombo failed 5
pairs on CI as SLOT_CONTRACT_MISMATCH). Combo slots are now recorded and
counted like wildcards instead of paired, the normalizer coerces slot names
to strings, and a pure spec locks both behaviors. Targeted fixtures remain
the way to cover combo semantics.
T-conn was planning-doc shorthand for the connectivity tier; test titles and
logs now say connectivity outright so CI output reads without tribal
knowledge.
Phase 5. A new informational (non-gating) workflow that reuses the repo's
setup-frontend/setup-playwright/setup-comfyui-server actions, then installs
every pack the manifest declares (jq loop over customNodeManifest.json, so a
new pack row installs itself with no workflow change) and boots ComfyUI with
--multi-user --cache-none before running browser_tests/tests/customNodes.

This makes the load and run tiers actually execute in CI instead of skipping
for want of the packs - the whole point of the suite. A pack whose deps fail
degrades to an honest skip rather than reddening the job.
…ny skip

A regression gate that lets a broken pack through as a skip is theater. Pack
clone/dependency failures now fail the job (array+loop instead of a
failure-swallowing jq|while pipe), and a post-run check fails the job if any
test was skipped - on this backend every tier is meant to run, so a skip
means a pack or devtools did not load. Drops the informational framing;
mark custom-nodes-e2e required in branch protection to block merges.
@socket-security

socket-security Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​start-server-and-test@​3.0.119910010094100

View full report

benjcooley and others added 3 commits August 11, 2026 10:38
…ict (#15027)

## Summary

A backend 5xx on `POST /prompt` now fails the run tier once, naming the
backend, instead of classifying `VALIDATION_FAIL` against the node under
test. 400 stays a pack-attributable validation reject.

## Changes

- **What**: `runWorkflow` keeps the captured `/prompt` status alongside
the summarized body (`PromptRejection`). When the final captured status
is >= 500 it throws `prompt submission failed server-side (HTTP <status>
POST /prompt) - <body message> - backend/environment fault, not a pack
validation reject` at both classification sites (double-refusal and
resolved-with-rejection), matching the fail-loud nomenclature of the
boot check (`cloud required boot request failed: HTTP ...`) and the
token check (`workspace token mint failed (HTTP ...)`). A captured 5xx
outranks a client-side throw: the backend demonstrably failed that
submission server-side. The client-flap retry and the stale-response
sequence guard are unchanged - the 500-then-200 retry pure test passes
untouched.

## Review Focus

- Attribution is the point. During the 2026-08-09 testcloud incident
(out-of-order migration, fixed by Comfy-Org/cloud#6486, left ingest
unable to INSERT jobs) every submission answered 500 `DATABASE_ERROR`
and the cloud gate reported **1,563 per-node VALIDATION_FAIL
"regressions" across 68 packs**, each labeled `not in cannotRunAlone; a
regression, or a new baseline entry` (run 31418724428). With this change
each affected test fails once with the true cause.
- One core-gate edge is deliberate: pack Python that crashes inside
`/prompt` validation also 500s, and now reads as an environment fault
rather than a per-node verdict. The tier still fails red either way;
only the attribution changes. The 400-with-`node_errors` path packs
actually exercise is untouched.
- Red-green: both new pure tests fail against the previous classifier
(resolve to `VALIDATION_FAIL`); 9/9 promptError pure tests and the
183-test pure/self-check battery pass with the change. No
`AUTO_RUN_ALLOWED_FAILURES` entry depends on a 5xx-shaped
`VALIDATION_FAIL` (all are `TIMEOUT`/`EXECUTION_ERROR`-shaped).

Fixes
[FE-1555](https://linear.app/comfyorg/issue/FE-1555/custom-node-suite-backend-5xx-misattributed-as-per-node-pack)
…-e2e-suite

# Conflicts:
#	browser_tests/fixtures/ComfyPage.ts
…15047)

## Summary

The shared coverage fixture called `stopJSCoverage` on a page the test
had legitimately closed, converting a passing test into a deterministic
CI failure.

## Changes

- **What**: `page.isClosed()` guard before `stopJSCoverage` in the
coverage-collecting `page` fixture override. A closed page has no
coverage to collect; behavior is unchanged for every test that leaves
its page open.

## Review Focus

- `customNodeSuite.pure.spec.ts › does not count same-document hash
navigation as a second boot` calls
`finalizeCloudCustomNodeBootGuard(page)`, whose `close` action is
`page.close()` by design (the enforcement boundary). The fixture
teardown then threw `coverage.stopJSCoverage: Target page, context or
browser has been closed` (`ComfyPage.ts:816`) - failing chromium shard
3/16 identically on three consecutive runs while passing locally,
because the fixture is gated on `COLLECT_COVERAGE=true` (CI shards
only).
- Red-green reproduced locally: `COLLECT_COVERAGE=true pnpm exec
playwright test
browser_tests/tests/customNodes/customNodeSuite.pure.spec.ts -g "hash
navigation"` fails on the base branch and passes with the guard; the
promptError pure specs confirm coverage collection is untouched for
tests that keep their page open.
benjcooley and others added 9 commits August 11, 2026 16:01
…15055)

## Summary

Follow-up to #15027 review: the incident regression test claimed the
production outage path but exercised the double-refusal classifier - its
mocked 500 body has no `node_errors`, and `app.queuePrompt` sets
`queueResultOverride = !nodeErrors` (`app.ts:1855`), so production
resolved that submission `true` and the real outage traversed the
resolved-with-rejection classifier.

## Changes

- **What**: the incident mock now returns `true` with the comment
corrected (asserts `evaluateCalls === 2`: no retry on the resolved
path); double-refusal keeps dedicated coverage in a separately named
test whose body genuinely makes production return `false` (`node_errors`
present), asserting the client-flap retry is spent (`evaluateCalls ===
3`) before the environment verdict. Both 5xx classification sites now
have path-faithful coverage; the fault message and `isServerSideFault`
contract assertions are unchanged.

## Review Focus

- Addresses
#15027 (comment)
as proposed. The test timings confirm the split: resolved path ~1ms,
double-refusal ~251ms (the 250ms retry beat). 10/10 promptError pure
specs green.
…#15056)

## Summary

Manual-run UI for the core gate: dispatch it at any frontend branch,
optionally as a tier/pack subset or against a ComfyUI candidate, and
read the verdict as a per-pack table in the job log and the run's
Summary panel.

## Changes

- **What**:
- `workflow_dispatch` inputs: `branch` (frontend ref under test - the
checkout honors it; every other event tests its own ref exactly as
before), `grep` (Playwright `-g` subset per the suite's title-pattern
design; passed via env, never shell-interpolated; the exact-count gate
logs and skips for subsets while the failed/skipped/flaky gates stay
active), `comfyui_ref` (probe a core candidate without editing the pin -
gating events stay pinned), and `enable_s14`/`enable_s15` toggles
(non-dispatch events keep both at `1`).
- Dispatches get a per-run concurrency group so two manual runs (e.g.
comparing branches) cannot cancel each other; PR pushes keep per-ref
supersede.
- A `Publish results table` step (`if: always()`) parses
`custom-nodes-results.json` and renders: branch tested / head SHA /
ComfyUI ref / event+actor / grep / S14-S15 / totals+duration, a per-pack
x tier (startup, all-nodes, curated, dynamic inputs, interaction)
verdict table, suite-wide rows (connectivity, smoke, manifest coverage,
harness self-checks, pure specs), and failed tests with their first
error line - to both the job log and `$GITHUB_STEP_SUMMARY`.

- **Deliberately not parameterized**: workers (the serial contract is a
suite invariant), retries (gate honesty), pack pins (the manifest owns
them), golden regeneration (the record workflows own it).

## Review Focus

- The "Run workflow" button appears only once the workflow file reaches
the default branch (post-#13389); until then dispatch via `gh workflow
run ci-tests-custom-nodes.yaml --ref <branch> -f branch=... -f
grep=...`.
- Summary renderer dry-run against a real gate artifact (run
31513986272): correctly pinpointed the VHS all-nodes geometry failure as
`FAIL 1/1` with all other cells PASS. Live dispatch validation on this
PR's branch to follow in a comment.
…sh (#15059)

## Summary

Fixes the two flakes in [run
31537261792](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/31537261792/job/93931291364)
via the suite's own escape hatches - ledger entries with named
mechanisms, not code changes.

## Changes

- **Geometry (was-node-suite)**: nine nodes measured 8-48px shorter in
`vue.h` than run 31518805275 recorded at identical suite code, while
**every widget height on the same nodes matched the baseline exactly** -
the flip sits in slot/section layout, not in any widget. That is the
failure message's ledger case verbatim ("delta flips between identical
runs"). Added `GEOMETRY_UNSTABLE_PATHS` entries relaxing only `vue.h`
per node, observed values cited; widget geometry stays strict.
- **Geometry (VHS)**: the earlier episode ([run
31513986272](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/31513986272),
rerun of the identical SHA passed) hit the preview widget **one index
below** the ledgered one (`widgets[4]/[8]/[7]`) plus un-ledgered
`VHS_LoadAudioUpload.vue.h` - covered with the same async default-media
mechanism so that flake cannot return either.
- **Connectivity sweep**: the uncaught page error is pack-owned -
`Custom-Scripts/js/mathExpression.js:31` draws
`app.nodeOutputs[this.id].value[0]` in `onDrawForeground`; the sweep
repeatedly clears graphs, ids recycle, and a stale value-less
`nodeOutputs` entry under a reused id throws. Allowlisted with the
mechanism, deliberately **without** `requiredConnectivityId`: a
frame-timing crash must not become a must-fire staleness obligation.
Upstream-report candidate for pysssss.
- Ledger-guard pure specs updated to pin the new sets (the WAS guard
asserts structurally that only `vue.h` is relaxed). 35/35 pure specs
green.

## Review Focus

- The WAS mechanism wording states exactly what is known (whole-node
height flip, widgets stable) without claiming a deeper cause not yet
observed - if the underlying Vue-renderer layout wobble gets
root-caused, these entries name precisely where to look.
## Summary

The cloud gate has zero genuine green runs and constant cancellations -
both are structural, not test-content, problems. This makes every run
serial and stops per-PR pushes from evicting each other.

## Changes

- **What**:
- **Drop the `pull_request` trigger.** Every automatic run shares one
serial resource (the smoke account's queue) behind a single-pending-slot
concurrency group, so per-PR pushes stampeded it: **16 of the last 40
runs were cancelled**, mostly PR-triggered runs evicting each other
(four in a row 21:03-21:18 on 2026-08-11), and no PR-triggered run
produced a verdict a PR acted on. PR-time custom-node coverage stays
with the core gate and the pure specs in the main shards; Cloud verdicts
come from suite-branch/main pushes, the merge queue, and dispatch.
- **Fold dispatches into the instance group.** The separate dispatch
group allowed a manual run to execute concurrently with an
instance-group run - observed live: dispatch 31541231667 co-tenant with
run 31530265854, which then sat 125m+ in its suite step. Co-tenancy
corrupts whole-queue observation (`waitForQueueQuiet`) for both runs.
The eviction pressure that justified the split dies with the per-PR
trigger.
- **`timeout-minutes: 150`** on the job (longest healthy suite ~110m): a
wedged run can no longer hold the serial group for GitHub's 360m
default.

## Review Focus

- This trades PR-time cloud signal (which the eviction data shows was
~never delivered) for runs that actually complete. If per-PR cloud
coverage is wanted later, it needs the prompt-scoped queue observation
change first - the serial contract is the binding constraint, not this
workflow.
- The fork-safety job `if:` still references `pull_request`; left in
place deliberately as defence-in-depth if the trigger ever returns.
…15073)

## Summary

The bare `VALIDATION_FAIL` flip-flop is a queue race with a verified
source chain - not a submit-time hook, and not a pack defect of the
failing node. The emitters are excluded as the cause; the victim keeps
executing under an allowed-failure entry.

## Changes

- **What**:
- `ImpactQueueTrigger` joins `AUTO_RUN_EXCLUDE`: its backend execution
emits `impact-add-queue` whenever its mode widget is on - **the
default** (`modules/impact/logics.py`, `doit`); Impact's JS answers with
a background `app.queuePrompt(0, 1)` (`js/common.js:113`); the frontend
re-entrancy guard (`app.ts:1640`, `if (this.processingQueue) return
false`) then refuses the harness's next submission with **no POST** -
the bare-`VALIDATION_FAIL` signature - pinning the failure on whichever
node queues next, through the 250ms retry when the background submission
is still processing.
- `ImpactQueueTriggerCountdown`'s inherited reason text is corrected:
the pack installs **no** `queuePrompt`/`beforeQueued`/`graphToPrompt`
hook anywhere in its JS; its real mechanism is the same backend-emit
chain.
- `ImpactSelectNthItemOfAnyList` - the victim, not the cause - moves
from `AUTO_RUN_EXCLUDE` to `AUTO_RUN_ALLOWED_FAILURES` (`outcomes:
['VALIDATION_FAIL']`): it keeps executing every run with full coverage,
the bare refusal is tolerated while the emitters sit excluded, and no
stale-entry alarm fires on passing runs.

## Review Focus

- Evidence: S9 failed with exactly this node in [run
31545300324](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/31545300324)
and passed in [run
31537458068](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/31537458068)
on the identical commit; every mechanism link above is cited to pinned
pack source or frontend source, addressing
#15073 (comment).
- A candidate follow-up outside this PR: the harness could wait for
`app.processingQueue` to clear before submitting, closing this race
class for every pack at the source.
…15083)

## Summary

The core gate's suite runs in 10-16m but setup adds ~12-16m; this caches
the pinned pack trees - the half of the original proposal the
measurements and review supported. (The pip-cache half was removed: its
key froze on the pinned requirements hash while torch floats, and live
measurement showed it wall-clock neutral.)

## Changes

- **What**: the manifest packs are fully determined by their SHA pins,
so their checked-out trees (sans `.git`) cache under
`cn-packs-v2-<manifest hash>` with a prefix fallback - a single-pin bump
still reuses every other pack. Hardened per review:
- Save gates on the **install step's** outcome (`always() &&
steps.install.outcome == 'success'`): a suite failure still saves a
complete tree; a failed or cancelled install can never freeze a partial
one under the immutable key.
- Save skips entirely on an exact-key restore (`cache-hit != 'true'`):
`actions/cache/save` re-tars and uploads unconditionally before the
server's duplicate-key rejection, so the steady-state green run now
costs nothing.
- Entries prune to the current manifest's pack set before saving - no
monotonic growth into the repo's shared 10GB budget.
- The integrity marker is a **sibling** `$cache_dir.pin` written last:
restored trees are byte-identical to fresh checkouts, truncated copies
fail the check, and a pack shipping its own top-level `.pin` installs
identically either way.
- Cache misses take exactly the pre-change install path; pack
`requirements.txt` installs run on every path so a dependency break
still fails the gate loudly.

## Review Focus

- v1-key validation (populate
[31549672781](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/31549672781)
/ restore
[31550313207](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/31550313207)):
all six `restore <pack> from cache` lines in the logs, install step 100s
-> 77s. A fresh populate+restore pair on the v2 schema will be
dispatched and linked here.
…15099)

## Summary

The cloud gate's dominant remaining failure class - 12x `at startup:
errorToasts` in the serialized measurement ([run
31541231667](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/31541231667))
- reports a bare element count, so testcloud-health noise and real
frontend errors are indistinguishable without a trace dive. Every red on
these surfaces now carries the surface's visible text.

## Changes

- **What**: `expectNoVisibleErrors` polls the surface's inner texts to
`[]` instead of `toHaveCount(0)`. Semantics preserved: a transient
surface that clears within the expect window still passes (proved by the
pure spec's 999ms transient case vs 5.0s persistent case); a persistent
one fails with its texts as the last polled value; the class-stable
`<context>: <surface>` label survives in the failure message for the
detection-proof greps; a mid-poll read race returns a sentinel rather
than throwing, so the poll retries exactly where `toHaveCount` would
have.
- New `errorSurfaces.pure.spec.ts` pins all three behaviors with a real
page and no backend; red-green proven - against the previous fixture the
text assertions fail (`toHaveCount` output carries no toast text).

## Review Focus

- Single enforcement point: every caller (startup checks, curated runs,
all four surfaces) inherits the diagnostics at once. With this in place,
the 12-failure class becomes one-glance triage: a wall of `HTTP
502`-flavored toasts is a testcloud ticket, anything else is ours.
The only failure in [run
31553987373](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/31553987373)
(this branch's last completed cloud run): `AudioConcatenate` rejects
with 400 `required_input_missing` for `audio1`, `audio2`, and
`direction` - it needs two wired audio inputs and belongs in
`cannotRunAlone`. Two-line ledger addition (both cloud ledger files),
mirroring the conclusion already reached independently on the
tier-isolation branch.

- `pnpm exec playwright test manifest.pure --project=chromium`: 15
passed
- Green-candidate evidence run dispatched from this branch:
[31651305020](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/31651305020)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Conflicts resolved: pnpm-lock.yaml regenerated from the merged
package.json; CustomerIoTelemetryProvider and its test converged on
main's version (main's bounded-await identify rework supersedes the
branch's earlier fire-and-forget attempt at the same non-stalling goal;
42/42 provider tests pass, vue-tsc clean).
@github-actions github-actions Bot added the risk:R3 PR risk grade (advisory shadow check; grader-owned) label Aug 12, 2026
Interaction profiles are recorder output and the cloud manifest is
gen:cloud-manifest output - same nature as the geometry goldens already
marked above; collapse them in review like the goldens.
The dev web API key is a public client identifier (it ships in every
bundle via src/config/firebase.ts), but the fixture duplicated the
literal because the SDK's storage lookup keys embed it. Export it from
the config module and import it, so review does not read a duplicated
key as a leaked secret and the value cannot drift.

@DrJKL DrJKL left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review verdict: request changes

This is an ambitious regression suite, but its current architecture mixes useful behavioral coverage with security-sensitive workflows, test-only authentication that bypasses the application contract, broad suppression/allowlisting, and a large body of change-detector tests. I do not think it is safe or maintainable as a required gate in this form.

I reviewed current head 8ad9a478 against main, with independent passes over type safety, CI security, authentication, Playwright isolation, generated baselines, manifests, and test quality. The concrete defects are annotated inline; this summary explains how they interact and why diagnosing failures will be unusually difficult.

1. Trust boundaries are crossed incorrectly

The Cloud recording workflow executes branch-controlled code while exposing account credentials. The nightly canaries execute floating upstream repositories and dependencies in a job holding a write-capable token. GitHub's security-hardening guidance recommends treating third-party and mutable code as untrusted and minimizing token permissions.

flowchart LR
  A[Collaborator pushes record branch] --> B[Checkout branch-controlled workflow and code]
  B --> C[Local setup actions and package scripts]
  C --> D[Playwright and custom-node execution]
  S[Smoke email and password] --> C
  T[Persisted GitHub credential] --> D
  D --> E[Credential exfiltration or repository mutation]
Loading

Hypothetical failure

A collaborator creates record/custom-nodes-cloud-debug and changes setup-frontend, build:cloud-e2e, or a Playwright import to send environment values to an external endpoint. The workflow intentionally supplies the smoke credentials to branch code. Exfiltration can happen while the recording still produces a normal-looking artifact.

Diagnostic problem

There may be no failed test or suspicious job output. Reviewers would have to audit every executable transitive import on the pushed branch after the credential has already been exposed. That is not a useful detection boundary.

Required direction

  • Remove the secret-bearing push trigger.
  • Execute secret-bearing workflow logic from trusted default-branch code only.
  • Set persist-credentials: false for jobs that execute external code.
  • Give test jobs contents: read only.
  • Move issue creation into a dependent fixed-code job with issues: write.

2. Cloud authentication is repaired after application code runs

The fixture seeds Firebase implementation details and then replaces outbound authorization headers. This may bootstrap custom-node tests, but it cannot demonstrate that the built Cloud application authenticated correctly.

flowchart TD
  A[Application chooses auth token] --> B[Outgoing API request]
  B --> C[Playwright route handler]
  C -->|replace Authorization| D[Fixture workspace JWT]
  D --> E[Cloud API accepts request]
  E --> F[Test reports green]
  A -. missing, stale, or wrong token .-> B
Loading

Hypothetical failure

authStore.getAuthHeader() regresses and returns null, a stale Firebase token, or a token for the wrong workspace. In production, authenticated API requests fail. In this suite, Playwright replaces the bad header with its cached workspace JWT, and all custom-node calls continue successfully.

Diagnostic problem

There is no red signal to diagnose. The test fixture has repaired the behavior whose correctness a Cloud run appears to imply. Conversely, after roughly one hour, the fixture's cached Firebase token can expire; when the workspace token needs renewal, the suite can produce a wall of unrelated authentication failures. Developers would need to distinguish:

  1. production token-selection regression,
  2. expired fixture credentials,
  3. Cloud outage,
  4. smoke-account state pollution, and
  5. actual custom-node failure.

The present logs do not establish that distinction.

Required direction

Either authenticate through the real application store/session path, or explicitly declare authentication out of scope and assert the application's original header before installing any rewrite. Refresh Firebase credentials before workspace-token renewal and key caches by origin plus account.

3. The suite contains many change-detector tests

Google's “Change-Detector Tests Considered Harmful” distinguishes tests that prove externally meaningful behavior from tests that merely report that today's implementation changed.

This PR explicitly adopts the latter model in several places:

  • Interaction profiles state that “whatever a node's JS does today is the baseline.”
  • Geometry snapshots lock tens of thousands of incidental renderer coordinates.
  • Pure specs duplicate complete exception-ledger inventories.
  • Workflow tests parse YAML and assert exact command, reporter, environment, and test-count literals.
  • Generated baselines are marked generated, reducing ordinary review visibility while remaining required-test inputs.
flowchart TD
  A[Implementation or environment changes] --> B[Snapshot or inventory differs]
  B --> C[Required gate turns red]
  C --> D[Developer inspects generated data and ledgers]
  D --> E{User behavior broken?}
  E -->|No| F[Noise: regenerate or expand exception ledger]
  E -->|Yes| G[Real signal]
  F --> H[Baseline becomes more permissive]
  H --> I[Future regression can be hidden]
  G --> J[Same initial symptom as noise]
Loading

Signal-versus-noise scenarios

Change Observed result Diagnostic ambiguity
Browser, font, rasterization, or device-pixel rounding changes Potentially thousands of geometry-coordinate deltas Same presentation as a real layout regression; developers must visually reconstruct whether each float matters
Pack initializes a custom element one task later Geometry/profile drift or a newly “unstable” node Could be harmless scheduling, a pack regression, or a frontend lifecycle regression
Pack adds a duplicate same-named slot Interaction profile may remain green Set conversion discards multiplicity, so a real topology change has no signal
Workflow command is refactored with identical collected tests Unit test fails YAML spelling changed, behavior did not; current PR is already red for this exact reason
PNG encoder changes filter/compression strategy “Pixel hash” fails Decoded pixels are identical, but compressed IDAT bytes differ
MP4 output is empty or corrupt Output hash remains green Non-PNG file content is never fetched or hashed
Exception ledger adds/removes one calibrated node Inventory test fails Test only proves two copies of the inventory differ; it does not evaluate behavior
A transient error toast appears for two seconds Error check eventually passes The user saw an error, but polling erased the evidence

Why this matters operationally

A required gate must make failures cheaper to classify than the bugs it catches. Here, broad snapshots and ledgers create an expensive recurring triage loop:

flowchart LR
  R[Red CI] --> A[Download large artifact]
  A --> B[Compare generated baseline]
  B --> C[Reproduce pinned core and packs]
  C --> D[Decide frontend vs pack vs environment]
  D --> E[Regenerate baseline or add exception]
  E --> F[Rerun 1 to 5 hour workflow]
  F -->|still red| A
Loading

The likely long-term response to recurring noise is expanding allowlists and unstable-path ledgers. That reduces confidence exactly when the suite becomes expensive enough that developers stop investigating each delta deeply.

Required direction

Replace exhaustive implementation snapshots with semantic behavioral invariants:

  • declared inputs remain present and usable,
  • expected sinks and outputs exist,
  • slot/widget counts preserve multiplicity,
  • coordinates are finite and elements do not overlap,
  • interaction topology stabilizes after lifecycle work,
  • decoded output content matches meaningful expectations,
  • every observed visible error is retained,
  • cleanup reaches a verified idle state.

Use a small curated visual/geometry set for historically risky nodes rather than exact coordinates for the entire ecosystem.

4. Error attribution creates false positives and false negatives

The suite currently:

  • polls until transient visible errors disappear,
  • suppresses prompt-related console messages using broad text patterns,
  • accepts websocket events without prompt IDs into the current run,
  • ignores backend-drain failure in normal teardown,
  • globally waits on a shared queue,
  • and permits a Cloud positive-control test to return without executing its control.
flowchart TD
  A[Test A submits prompt] --> B[Test A teardown times out silently]
  B --> C[Test B starts on dirty backend]
  C --> D[Late event or error reaches Test B]
  D --> E{Classifier}
  E -->|matches foreign-noise regex| F[Error silently discarded]
  E -->|does not match| G[Test B fails]
  F --> H[False green]
  G --> I[False attribution to Test B]
Loading

Hypothetical failure

Pack A starts a non-interruptible prompt. drainBackendToIdle() returns 1, but afterEach discards it. Pack B starts, receives Pack A's late bare executing event and a prompt error. The event can be retained because it lacks prompt_id; the console error can be removed because it matches “foreign execution noise.” Depending on ordering, B can falsely pass, falsely report partial execution, or fail for A's error.

Diagnostic problem

The same underlying contamination produces three different symptoms. Developers will not know whether to inspect Pack A, Pack B, prompt attribution, queue isolation, or the allowlist. Historical run-ID comments do not solve this because they describe prior incidents rather than establish runtime provenance.

Required direction

flowchart LR
  A[Submit prompt] --> B[Capture prompt ID]
  B --> C[Collect only events for that ID]
  C --> D[Retain every visible and console error]
  D --> E[Assert terminal state]
  E --> F[Assert owned work is idle]
  F --> G[Report prompt, node, tier, and first causal error]
Loading

Prefer an isolated backend. Otherwise carry explicit prompt ownership end-to-end and reject unattributable events. Teardown must throw when owned work remains.

5. Type safety stops at the most important boundaries

No newly introduced any/as any was found, but several assertions hide unvalidated external data:

  • extension-sentinel JSON is asserted to Record<string, string[]>,
  • geometry JSON is asserted to PackGeometryFile,
  • output-hash JSON is assigned to CuratedOutputHashes,
  • workspace API response enums are widened to string,
  • generated baseline provenance is present but not validated against the active environment.

These are not harmless compiler conveniences. They convert malformed or stale external data into trusted domain objects without runtime proof.

Hypothetical failure

A sentinel key is misspelled. Manifest generation succeeds because unmatched keys are not rejected. The corresponding pack receives expectedExtensions: [], and frontend-extension loading is no longer checked. CI becomes greener by losing coverage.

Required direction

Parse as unknown, validate at ingress, reject unknown keys, require exactly one core row, and reuse production/generated API schemas rather than duplicating looser interfaces.

6. Output-regression claims exceed actual coverage

The PNG path hashes compressed IDAT payloads, not decoded pixels. The non-PNG path canonicalizes file references to extensions without reading content.

flowchart TD
  A[Sink payload] --> B{File extension}
  B -->|PNG| C[Hash compressed IDAT bytes]
  B -->|MP4, audio, GIF| D[Keep extension only]
  C --> E[False red when encoding changes]
  D --> F[False green when content changes]
Loading

A trustworthy content tier should decode PNGs to normalized RGBA and hash the decoded bytes. It should hash or decode supported video/audio content, or fail explicitly and narrow its documented scope. It should also require declared sinks; iterating an empty sink list is not evidence.

7. Baseline recording can publish partial or stale data

Record steps use continue-on-error. Geometry files are cleared, but interaction profiles are not. Artifact checks establish only that a directory contains files, not that every manifest pack was recorded with matching schema and provenance. The Cloud step ceilings total 380 minutes before setup while the job ceiling is 350 minutes, so the job can be cancelled before upload despite comments claiming uploads remain reachable.

Hypothetical failure

Recording crashes after the first pack. Geometry artifact contains one fresh file. Interaction-profile artifact contains one fresh file plus old committed files for all remaining packs. Both uploads look complete to a human downloading them.

Diagnostic problem

A later comparison can report drift against mixed provenance. Developers must infer which files were generated in which run. The recorded metadata is not sufficiently validated to reject the mixture.

Required direction

Write every product into a fresh run-specific directory, clear all destinations, validate the exact pack set/count/schema/provenance, and upload only after validation passes.

8. Comment volume violates repository guidance

A conservative diff-only prefix count found 1,623 added comment-only lines across 17,166 added TS/YAML/JS/Vue lines (~9.5%, excluding JSON and Markdown). Largest concentrations:

File Added comment-only lines
allNodes.spec.ts 165
ci-tests-custom-nodes-cloud.yaml 129
ci-tests-custom-nodes.yaml 112
ci-nightly-custom-nodes-canary.yaml 95
consoleErrorLedger.ts 79
customNode.regression.spec.ts 78
record-custom-nodes-geometry-cloud.yaml 72
connectivity.spec.ts 72

Many comments are:

  • historical run-ID incident journals,
  • explanations of immediately following commands,
  • repeated descriptions of names and types,
  • claims that executable assertions should establish,
  • multi-paragraph defenses of unusual return codes,
  • and stale operational instructions embedded beside implementation.

This directly conflicts with AGENTS.md lines 208–221: do not add multi-line comments for trivial changes, do not paraphrase test setup, and delete comments that merely explain obvious code.

Why this impairs diagnosis

When comments contain many historical explanations, developers cannot know which statements remain current. For example, a comment may say a timeout keeps uploads reachable while the arithmetic of current step/job ceilings proves otherwise. The comment increases confidence while concealing drift.

Move incident timelines and run IDs to the design document or PR discussion. Keep only durable, non-obvious constraints. If an API needs a paragraph warning callers not to ignore its return value, change the API to throw or use a result type that callers must handle.

Required changes before approval

  1. Correct secret and repository-token trust boundaries.
  2. Decide whether Cloud auth is setup or tested behavior; stop silently repairing it if it is behavior.
  3. Replace broad geometry/interaction change detectors with semantic invariants and a small curated visual set.
  4. Make event/error attribution prompt-scoped and teardown fail-closed.
  5. Decode output content before hashing and require declared sinks.
  6. Runtime-validate every JSON/YAML/API boundary.
  7. Remove inventory and YAML-text change-detector tests.
  8. Make record artifacts complete, fresh, and provenance-validated.
  9. Remove incident-log and code-paraphrasing comments.
  10. Restore required checks: current head fails scripts/playwright-cloud-trace.test.ts, and action pin validation rejects the two cache actions.

Validation evidence

  • pnpm typecheck:browser — passed
  • targeted ESLint over changed custom-node/auth/script files — passed
  • 65 targeted pure Playwright tests — passed
  • scripts/cloud-manifest.test.ts — 33 passed
  • scripts/playwright-cloud-trace.test.ts1 failed
  • GitHub pin validation — failed on actions/cache/restore@v5 and actions/cache/save@v5
  • git diff --check origin/main...HEAD — passed

# default branch; pushing a record/custom-nodes-cloud-* ref runs this
# file from that ref (the core record workflow's escape hatch). Delete
# the ref after collecting the artifact.
push:

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.

Blocking — secret-bearing workflow executes branch-controlled code.

A collaborator can push record/custom-nodes-cloud-*; this workflow then checks out that ref and runs its local actions, package scripts, Vite build, and Playwright code with SMOKE_ACCOUNT_EMAIL and SMOKE_ACCOUNT_PASSWORD available. Repository write access does not imply permission to read shared account credentials.

Hypothetical: a branch modifies setup-frontend or build:cloud-e2e to POST environment values elsewhere. The run still looks like a recording run, and the exfiltration may leave no useful failure signal. Reviewers cannot differentiate a legitimate branch implementation from credential-stealing code after the fact.

Remove the push escape hatch. Keep the workflow implementation on the trusted default branch and accept only inert inputs/artifacts, or use an environment with required approval and no arbitrary ref checkout. See GitHub's secure use reference.

runs-on: ubuntu-latest
permissions:
contents: read
issues: write

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.

Blocking — floating third-party code runs with a write-capable repository token.

This job installs current ComfyUI/custom-node code and unpinned Python requirements, then executes them while the job has issues: write. actions/checkout also persists credentials by default. A compromised upstream dependency can recover that credential and mutate repository issues.

Split this into (1) a read-only test job using persist-credentials: false, and (2) a dependent issue-reporting job with issues: write that executes only fixed repository code and consumes the prior job's status. This makes a canary compromise unable to cross the repository write boundary.

# unconfigured secret is a hard activation failure.
name: 'CI: Tests Custom Nodes Cloud'

on:

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.

Blocking — this cannot provide the advertised PR gate.

There is no pull_request trigger, so ordinary PR commits receive no Cloud result. merge_group helps only when every merge goes through an enabled merge queue; a branch can otherwise merge with broken Cloud extension loading/auth/proxy behavior and discover it only after main is updated.

Hypothetical: a PR changes a Cloud-only API path. Core and pure tests pass, the PR has no Cloud check, and the push-to-main run becomes the first signal. Diagnostics now start after the bad revision is shared, and developers must distinguish that PR from concurrent main changes.

Either restore same-repository pull_request execution with a scalable account/resource strategy, enforce merge queue universally, or stop describing this as a PR gating check.

cached.expiresAt - Date.now() > WORKSPACE_TOKEN_MIN_REMAINING_MS
)
return cached
workspaceSession = mintWorkspaceSession(appUrl, user).catch(

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.

Blocking — workspace-token refresh can use an expired Firebase ID token.

workspaceSession is refreshed when its JWT nears expiry, but user comes from the module-cached smokeUser; its Firebase access token is never refreshed. Firebase ID tokens normally expire after about one hour (Firebase session documentation), while this workflow allows 110–150-minute runs.

Scenario: at minute 65 the workspace JWT needs reminting. /api/auth/token receives the original expired Firebase bearer and every remaining pack fails. The resulting wall of auth failures looks like Cloud instability or a frontend regression rather than deterministic fixture expiration.

Refresh/re-sign the Firebase user before reminting, and key caches by account plus origin instead of process-global identity.

): Promise<void> {
const apiPrefix = new URL('/api/', appUrl).toString()
await page.route(
(url) => shouldRewriteAuthHeader(url, apiPrefix),

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.

Blocking — the fixture repairs the application's authorization behavior on the wire.

Every matching API request has its Authorization header replaced with the fixture's workspace JWT. If authStore selects no token, a stale token, or the wrong token, this suite still succeeds because Playwright fixes the request after application code emits it.

That creates a zero-signal failure mode: a production auth regression remains green. If auth is intentionally outside this suite's scope, assert the app's generated header once before installing any rewrite and document the exclusion. Preferably seed state through the production session/store path and remove the route rewrite entirely.

} from '@e2e/fixtures/customNode/geometry'
import { loadManifest } from '@e2e/fixtures/customNode/manifest'

test('ledgers only the source-driven VHS preview height paths', () => {

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.

Change-detector inventory test.

This restates the complete exception ledger—pack names, node names, and field paths. Any legitimate calibration change requires editing production constants and their duplicated test inventory, but the test does not prove that ignored paths are narrow or that adjacent paths remain strict.

Keep one representative behavioral test: a ledgered path is ignored and a neighboring unledgered path fails. Remove exhaustive inventory duplication. The ledger diff itself is what reviewers should inspect.

})
})

test('routes each artifact-proven mechanism to every observed renderer', () => {

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.

Another change-detector inventory.

This snapshots all current exception members rather than a public behavior. Adding/removing a calibrated node breaks the test by design and tells the developer only that two copies differ. It gives no independent evidence that renderer partitioning, staleness, or topology matching is correct; those behaviors are tested below.

Delete the inventory assertion and retain small composable cases for renderer selection, stale entries, and exact transition matching.

trackSubmittedPrompts(comfyPage.page)
})

// Leave the shared backend idle after every test so the next test's fresh

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.

Repository-guideline violation: excessive incident/paraphrase comments.

A conservative diff count finds 1,623 added comment-only lines in changed TS/YAML/JS/Vue files. This file alone adds about 165. Many blocks narrate the next statement, preserve CI run IDs/incidents, or compensate for APIs whose failure mode is not encoded in their type.

AGENTS.md explicitly forbids multi-line comments for trivial changes and comments paraphrasing setup lines. Move historical diagnostics to the design document/PR, keep only durable non-obvious rationale, and improve APIs so callers cannot silently misuse return codes.

# provenance stamps 'unrecorded' (allNodes.spec.ts); the row's deployRef
# is recorded as the pin.
- name: Record cloud geometry baselines
continue-on-error: true

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.

Record artifacts can be partial or stale while looking complete.

The geometry destination is cleared, but interaction profiles are not. All three producers use continue-on-error, and upload checks only for some file(s). A crash after one pack can publish a partial geometry set; failed interaction packs can be silently supplied by committed checkout files.

Clear every destination first, write into a fresh run-specific directory, and validate the exact manifest pack set, schema, provenance, and node counts before upload. Also note that the three step ceilings total 380 minutes before setup while the job ceiling is 350, so the job can be cancelled before uploads despite comments promising reachability.

# check - a stale or partial entry re-clones.
- name: Restore manifest pack cache
id: pack-cache
uses: actions/cache/restore@v5

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.

Required check currently fails action pin validation.

Repository policy requires third-party actions to be pinned by full SHA. CI reports this line and the corresponding actions/cache/save@v5 line as unpinned. Pin both to the reviewed commit SHA; tags are mutable supply-chain inputs.

CodeJuggernaut and others added 2 commits August 13, 2026 11:45
Move the shared public dev Firebase key into a pure module so Playwright fixture discovery does not evaluate the Vite-only build define.
## Summary

Makes Custom Nodes E2E results independently attributable to S1, S2, S3,
S9, and S14. Previously, one per-pack Playwright row ran several of
those contracts together, so one failure could obscure which tier failed
and could prevent later tier checks from producing their own result.
This PR gives each tier its own test and page lifecycle, while retaining
the same pack coverage and environment boundaries.

## Changes

- **What**:
- Replaces the combined all-node row with independent S1, S2, S3, S9,
and S14 tests. Each tier receives a fresh application page and emits
per-pack, per-tier results.
- Preserves normal Core coverage: S14 and S15 remain active. Normal
Cloud keeps its 87 manifest packs and 102 active S1-S12 test rows; Cloud
S13-S15 remain deferred.
- Publishes the independent tier results in the Core and Cloud job
summaries, rather than collapsing them into one `all nodes` cell.
- Adds isolated controlled-break patches for the split tiers, so a
deliberate tier failure can be attributed to that tier.
- Keeps Cloud S9 baseline handling strict while correcting source-proven
stale outcomes. New or unexpectedly clean outcomes still fail.
- Preserves geometry baseline recording after the split: explicit
recorder mode writes S14 baselines, while normal Cloud S14 remains
disabled.
- **Breaking**: none. No production behavior changes.
- **Dependencies**: none.

## Review Focus

- Confirm S1, S2, S3, S9, and S14 are independently selected and
reported, with no tier silently dropped from the normal Core or Cloud
scopes.
- Confirm one tier failure cannot reuse its page or suppress a later
tier result.
- Confirm the Cloud manifest remains 87 packs, and its 102-test
collection reflects the split test shape rather than lost pack coverage.
- Confirm normal Cloud does not enable S14 or S15. Geometry recording is
a separate `CN_GEOMETRY=record` operation that writes baselines and
deliberately fails its recording step.
- Confirm Cloud S9 baseline changes remain fail-closed for any outcome
not explicitly supported by the current evidence.

## Validation

- `vitest run scripts/playwright-cloud-trace.test.ts`: 25 passed.
- Core and Cloud geometry-record selectors each collect exactly one S14
test.
- Normal Cloud collection remains 102 tests with no S14 test.
- Browser typecheck, scoped ESLint/Oxlint, YAML parsing, formatting, and
`git diff --check` passed.
- Fresh Principal Engineer, Senior QA, Senior Product, and adversarial
reviews passed on the final recorder repair.

## Screenshots

Not applicable.

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: benjcooley <benjcooley@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

@christian-byrne christian-byrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First full review of this PR from me. Reviewed at b4494ecb61, with deps installed, vitest and playwright --list run, and mutations used where a claim needed a control. All mutations reverted.

Reviewing it because it is the vehicle: #15200 and #15155 land into this branch, so this is what eventually reaches main.

The ten findings from Alex's review that survive the cloud cut are all still live

None has been addressed in the 254 commits since he reviewed at 8ad9a478. Details inline; the summary:

Finding 6 has moved backwards. The error-toast tolerance is now documented as intended at errorSurfaces.ts:42-47 and pinned by a passing test titled "a transient toast that clears within the window still passes". Neither playwright config sets an expect: block, so the window is Playwright's default 5000 ms per surface per call.

Findings 7 and 8 compose into a specific false PASS. A discarded drain timeout leaves work on the shared backend; each test gets a fresh page so __cnIdBase restarts at 0 and node ids repeat; the straggler's bare executing is then admitted into the next test's verdict. I reproduced both halves of 8 against the real classifyRun.

Finding 22 is one line. .pinact.yaml is byte-identical to main and its ignore list names actions/cache; pinact matches names exactly, and actions/cache/restore and actions/cache/save are different names. main uses neither subpath anywhere, so this PR is their first consumer.

Findings 11, 15, 16, 19 and 20 are verbatim unchanged. Finding 18's "currently red" half is fixed — the script suites are 62 passed / 0 failed — but its substantive claim is now larger, and I have a control for it inline.

New findings nobody has looked at

The highest-value one is on manifest.ts:373: the pack-ledger staleness rail does not hold for 5 of the 6 core packs, because it validates against the union of both environments' manifests. I proved it by dropping a pack from the core manifest and watching the rail still pass. The contract is asserted six times in this codebase.

The largest available deletion is on geometry.pure.spec.ts: 19 *.pure.spec.ts files, 4,721 lines, 191 tests, are collected into chromium and therefore into e2e-status — a required check on main — on every PR in the repo. They are vitest tests running under Playwright workers with retries: 3.

Also: the detection proofs never run on anything that gates; the tier split runs all six packs on one shared page; the three snapshot tiers are no-ops off CI so a red cannot be reproduced locally; and geometry baselines carry provenance that is written and never read while both sibling loaders validate theirs.

A correction to something I told Ben on #15200

I said the rebase was semantic because #15104 and #15057 would have made some of this scaffolding redundant. That is wrong. I grepped every file this branch adds or modifies for mockReset|clearAllMocks|resetAllMocks|createTestingPinia|setActivePinia: three hits, none of which count — a README, vite.config.mts itself, and a pre-existing test the branch adds ten lines to. This line adds essentially zero vitest suites under src/; its new vitest files are all under scripts/. The consolidations do not intersect it.

Merge state

The only conflict is pnpm-lock.yaml. Trial merge against origin/main: package.json, .oxfmtrc.json and pnpm-workspace.yaml all auto-merge, and I checked the semantic outcomes — main's i18n migration wins correctly and the branch's script and catalog additions all survive. Regenerate the lockfile and the merge is clean.

No required check has ever run on this PR. test, lint-and-format, e2e-status and website-e2e are all absent at head, because GitHub cannot build a merge commit while the PR conflicts. The core gate itself has not run at head either — CI: Tests Custom Nodes last succeeded at 56ea1d1d, the commit before #15155 merged, so the tier split has never had a green core gate.

The cloud gate is running right now. CI: Tests Custom Nodes Cloud started at 21:19Z on this head and was still in progress an hour and forty minutes later; the prior run was cancelled. That is ADR-001's exact symptom, occurring on the vehicle PR after the decision, holding the single-slot concurrency group against every other consumer. #15200 deletes it, but #15200 is a draft that conflicts against its own base.

Alignment with the settled direction

8,199 lines of cloud, canary and S15 surface are still in the diff. #15200 closes almost all of it, with one exception worth naming: the rules-over-snapshots decision is reflected in no code, in this PR or any of its satellites. What exists is 1.14 MB of geometry baselines across six files and 4,126 lines of interaction profiles. #15200 touches geometry.ts by +3/−10, removing only the cloud path. That is the largest gap between the agreed direction and the code.

Two smaller items: CLOUD HEAD survives as a live axis in docs/custom-node-regression-suite.md, and after #15200's cut src/config/firebaseConstants.ts becomes an orphaned two-line production module whose only remaining consumer is the file that was refactored to import it.

One production fix is hostage in here

useImageUploadWidget.ts:126-133 is a real user-facing bug fix — without the guard an unbound combo value makes the app request a file literally named "undefined" — with two proper vitest cases. Seven lines, independently landable today, currently blocked behind 73,273 lines and a conflicting merge. Worth splitting out.

Checked and sound, so nobody re-litigates

diffGeometry's two-way reconciliation and its ignored-path construction including the array-index branch; comparePackProfiles fail-closed on a missing baseline and two-way on both node sets; cannotRunAlone genuinely two-way in all three directions; the manifest covers every registered pack test closing the new-pack-lands-with-no-coverage hole; EXPECTED_TESTS as a real collected-count rail, confirmed at 33 with --list; and the attribution self-check's stimulus-landed positive control at customNode.regression.spec.ts:498, which refuses to trust its own PASS until it proves the injection happened. That last one is the right discipline — it is just pointed at the case the filter already handles rather than the one that bites.

Not reviewed

autoRun.ts (368 lines), typePairing.ts (298), connectivity.spec.ts (667) and dynamicInputs.spec.ts (284) beyond grep level. Those remain unread by anyone.

return entries
}

export function loadAllManifestPackNames(): string[] {

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.

issue: this rail does not hold for 5 of the 6 core packs, and I proved it.

loadAllManifestPackNames() concatenates core and cloud pack names unconditionally, 6 + 87 = 93.
assertPackLedgerKeys then case-folds and checks membership against that union. Five of the six core
packs also appear in the cloud manifest under lower-cased dirnames that fold to the same key, so
their ledger entries can never go stale.

Mutation, run against head:

baseline:                                        93 names, RAIL PASSED
drop ComfyUI-VideoHelperSuite from the core manifest (6 -> 5 rows):
                                                 92 names, RAIL PASSED (still)

GEOMETRY_UNSTABLE_PATHS['ComfyUI-VideoHelperSuite'] — 6 nodes, 20 individually-ignored geometry
fields — keeps passing the staleness check after its pack has left the core manifest entirely,
because comfyui-videohelpersuite is still row 40-something of the cloud one.

The contract is stated six times in this codebase as "an entry whose pack leaves the manifest reds".
Concrete consequence: a pin bump that drops or renames a core pack silently orphans its whole
exception ledger, which then suppresses assertions for a pack nobody is testing.

Fix is to key the rail to the active environment — loadManifest().map(e => e.pack), not the union.
Note this self-heals once the cloud half is gone, but #15200 will make this function throw if it is
not updated at the same time.

)
}

function readCoreManifest(): CoreManifestEntry[] {

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.

issue: readCoreManifest is fail-open on an empty array; the cloud loader ten lines above is fail-closed.

assertCloudManifestShape explicitly rejects manifest.packs.length === 0 at :331. This has no
equivalent — JSON.parse(...) as CoreManifestEntry[] then entries.forEach(assertCoreEntry), which
is a no-op on [].

Mutation with the core manifest set to []:

core  loadManifest() -> 0 entries, NO THROW
cloud (packs: [])    -> THREW: ... is malformed (expected { coreDisabledNodes, packs, ... })

With zero entries every for (const entry of loadManifest()) registers no tests, and the five tier
tests pass vacuously over zero packs.

EXPECTED_TESTS in the workflow catches the count drop in CI, which contains it — but it does not
apply to pnpm test:custom-nodes locally, and it is skipped entirely when inputs.grep is set. One
length === 0 check here makes the two loaders symmetric.

// keeps toHaveCount(0)'s tolerance - a transient surface that clears within
// the expect timeout still passes - while a persistent one fails with its
// visible text as the last polled value.
export async function expectNoVisibleErrors(

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.

issue: this finding has moved backwards since Alex raised it.

The poll still retries until the surface list is empty, so an error toast that appears and clears
inside the window passes. What changed is that the behaviour is now documented as intended at
:42-47 — "a transient surface that clears within the expect timeout still passes" — and pinned by
a passing test at errorSurfaces.pure.spec.ts:50-60 titled "a transient toast that clears within
the window still passes".

Neither playwright config sets an expect: block, so the tolerance is Playwright's default 5000 ms,
per surface, per call. Five seconds is a long time for a user-visible error to be invisible to CI.

Christian's ruling on 2026-08-13 was to replace the mechanism rather than answer the
fail-versus-pass question: assert on the deterministic console/pageerror ledger, which this suite
already maintains, instead of polling the DOM for a transient. That dissolves the calibration
argument. It also repairs consoleErrorLedger.ts:466, which currently justifies filtering
PromptExecutionError on the grounds that "the visible error SURFACES are still asserted separately
by expectNoVisibleErrors" — i.e. it leans on this function being strict.

// `executing` strings - stay, and graph membership still vets them);
// otherwise the legacy seen-set exclusion.
capturedPromptId !== undefined
? event.prompt_id === undefined || event.prompt_id === capturedPromptId

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.

issue: both directions of this reproduce. Control below, using the real classifyRun and toPromptEvent.

curated tier (graphNodeIds undefined), unscoped foreign error ->
  {"outcome":"EXECUTION_ERROR", "error":{"exceptionType":"ForeignError","nodeId":"424242"}}
auto-run tier (graphNodeIds=[1,2]), bare straggler 'executing' ->
  {"outcome":"PASS","executedNodes":["1","2"]}

False red. customNode.regression.spec.ts:276 calls runWorkflow without graphNodeIds, so the
graph-membership guard at runResult.ts:103-105 is inert and an unscoped execution_error naming a
node that is not in the graph reds the curated test and blames the pack.

False green. executedNodesFrom (runResult.ts:59-68) never consults graphNodeIds at all, so
a late bare executing for a recycled in-graph id inflates executedNodes and turns a genuine
PARTIAL into a PASS. Passing graphNodeIds does not help.

The self-check at :457 does not cover this: it injects a foreign event that carries a
prompt_id ('cn-foreign-self-check', :479), which is the case the filter already handles. It is
blind to the unscoped case, which is the one that bites.

This composes with the discarded drain timeout: work left running on the shared backend, each test
getting a fresh page so __cnIdBase restarts at 0 and node ids repeat, and the straggler's bare
executing admitted into the next test's verdict. False PASS, no symptom, from two separately
flagged defects neither of which is fixed.

for (const id of running) await window.app!.api.interrupt(id)
for (const id of pending) await window.app!.api.deleteItem('queue', id)
}, owned)
if (Date.now() >= deadline) return 1

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.

issue: five of six teardown sites still discard this.

drainBackendToIdle returns 1 on timeout. Exactly one caller checks it —
allNodes.spec.ts:1965, inside the auto-run tier. The other six discard it: allNodes.spec.ts:373
and :720, connectivity.spec.ts:71, coreSmoke.spec.ts:36, customNode.regression.spec.ts:89,
interactionProfiles.spec.ts:207.

The comment at allNodes.spec.ts:370-372 justifies discarding on the grounds that "the auto-run
tier's 150s guard surfaces that". That guard is in a different test, on a different page, and does
not run for the S1/S2/S3/S14 tiers, connectivity, coreSmoke, regression, or interaction profiles.

See the ComfyTarget.ts thread for how this composes into a false PASS.

// second needs declaring - the first is already in the manifest row - and both
// are skipped so the staleness guards do not force every ledger to fork per
// environment. Anything else absent still fires.
const PIN_SKEWED_LEDGER_NODES: Record<string, string> = {

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.

issue: this is the one exception ledger in the suite with no staleness rail, and it sits inside the staleness checker.

Every other ledger here is registration-guarded — GEOMETRY_UNSTABLE_NODES and ..._PATHS at
allNodes.spec.ts:771-783, MOUNT_WIDGET_ALLOWLIST at :790, INTERACTION_UNSTABLE_NODES at
interactionProfiles.spec.ts:209, AUTO_RUN_UNSTABLE_NODES and AUTO_RUN_ALLOWED_FAILURES at
:1735-1757, cannotRunAlone two-way at :1773. PIN_SKEWED_LEDGER_NODES has none.

Its single entry hardcodes ComfyUI-KJNodes.ContextWindowsVisualizerKJ with the reason "the cloud
deployRef 377ed49f predates it". Nothing checks that 377ed49f is still the deployRef, that the node
still exists, or that the exemption is still needed. The moment the deployRef is bumped past
2026-06-17 this permanently suppresses the staleness check for that node — inside
stalenessCheckedKeys, the function every other ledger's rail routes through.

#15200 deletes this file, so the answer may be "delete". But stalenessCheckedKeys has seven callers
in allNodes.spec.ts plus one in the auto-run reconciliation, so whatever replaces it needs care.

return `${geometryDir()}${pack}.json`
}

export function loadPackGeometry(pack: string): PackGeometryFile | null {

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.

issue: geometry provenance is written and never read, unlike both sibling loaders.

This is Alex's body-level item that had no inline anchor. loadPackProfiles
(interactionProfiles.ts:89-100) validates schema !== 1 || !recordedAt?.core and throws;
loadOutputHashes (outputHashes.ts:168-174) does the same. loadPackGeometry is a bare
JSON.parse(...) as PackGeometryFile. Repo-wide, the geometry recordedAt is written at
allNodes.spec.ts:1039 and read by nothing.

There is already a UI for the failure. All six baselines carry
recordedAt.core = b08e6cf35fac50d3ca8470dffb3f9a1fbb7187d2, matching the gate's pin at
ci-tests-custom-nodes.yaml:166 — but :42 exposes a comfyui_ref dispatch input. Dispatch with
any other core SHA and S14 compares against a baseline recorded under a different ComfyUI, producing
reds nobody can attribute, while the run summary at :512 prints COMFYUI_REF_USED next to a
baseline whose provenance it never checked.

Validate schema and recordedAt like the siblings, and red when
recordedAt.core !== COMFYUI_REF_USED.

throw new Error(
`geometry baselines recorded for ${entry.pack} - commit ${packGeometryRelativePath(entry.pack)} and re-run without CN_GEOMETRY`
)
} else if (!process.env.CI) {

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.

issue: all three snapshot tiers are no-ops off CI, so a red cannot be reproduced locally.

S14 here, S13 at interactionProfiles.spec.ts:231, S15 at customNode.regression.spec.ts:361 — all
gate their compare on process.env.CI and log-and-return otherwise. playwright.chrome.config.ts,
which every documented local script uses (test:custom-nodes, :watch, :debug, :local), sets no
CI. So a green pnpm test:custom-nodes:local is compatible with S13, S14 and S15 being entirely
broken, and the only way to see a geometry red is to dispatch a CI run.

This is worth connecting to the growing exception ledger. An engineer who cannot iterate locally on a
0.01px tolerance against a 1.14 MB baseline will ledger the node instead. GEOMETRY_UNSTABLE_PATHS
now relaxes 9 WAS nodes at the whole vue subtree — those nodes have zero Vue geometry coverage. The
ledger growth is a symptom of this as much as of the one-rAF sampling.

const observed: Record<string, NodeInteractionProfile> = {}
for (let start = 0; start < plans.length; start += PROBE_CHUNK) {
const chunk = plans.slice(start, start + PROBE_CHUNK)
const chunkResults = await comfyPage.page.evaluate((probePlans) => {

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.

issue: still one page.evaluate, so deferred pack init still never runs.

Create, connect-first, measure, connect-last, measure, disconnect, measure, remove — all inside the
single callback from :94 to :202. No yields, no phase split, no settle loop. Any pack that defers
its setup to a microtask or a timer has that work happen after every measurement, so the committed
baselines encode pre-init state and the tier cannot see the thing it exists to watch.

Unchanged since Alex raised it.

(rule) =>
rule.requiredConnectivityId !== undefined &&
!errors.some((error) => ruleMatches(rule, error))
)

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.

issue: this filter's justification depends on a mechanism that is documented to be tolerant.

isForeignExecutionNoise drops /PromptExecutionError/, /Prompt execution failed/ and
/Failed to load resource.*\/api\/prompt/ from the mount, persistence and wiring tiers, on the
grounds that those tiers queue nothing. The comment then says, verbatim, "This is not error
suppression: the visible error SURFACES (overlay/dialog/toast) are still asserted separately by
expectNoVisibleErrors."

But expectNoVisibleErrors passes any surface that clears within 5 s, and
errorSurfaces.ts:42-47 now documents that as intended.

The suite itself acknowledges packs queue unprompted — interactionProfiles.spec.ts:205-206, "the
guard for pack JS that queues behind our back while being probed". A pack that queues during the
mount tier and fails produces a PromptExecutionError that is filtered from the console assertion by
design and admitted by the toast assertion if the toast auto-dismisses. Both mechanisms that are
supposed to cover each other are open at once.

Making expectNoVisibleErrors fail-closed on first observation makes this justification true.

zeus-onl pushed a commit to zeus-onl/ComfyUI_frontend that referenced this pull request Aug 21, 2026
…Comfy-Org#15225)

## Summary

Adds a local-backend custom-node E2E suite with two complementary
populations. **Not a PR gate yet**: the workflow runs on a nightly
schedule and manual dispatch only - PRs neither trigger it nor wait on
any of its checks, and none of its checks are required in branch
protection. Within a run it fails closed (an install failure or skipped
tier is red).

- **Core depth:** the original six pinned packs retain S1-S13 and S15.
S14 geometry snapshots remain removed by team decision.
- **Cloud breadth:** the pinned Cloud snapshot has 87 joined manifest
rows; 83 run across five fixed shards on a local CPU ComfyUI backend.
One source row is unjoined and four rows are explicitly quarantined.

S15 is restored inside the six Core curated workflow tests that already
execute. It adds output comparison, not another prompt or Playwright
test, so its incremental runtime is negligible.

## Changes

- Pins ComfyUI core, pack sources, registry artifacts, staged inputs,
worker count, retry count, and shard composition.
- Keeps each cloud pack in a stable shared Python environment; changing
a quarantine entry cannot reshuffle other packs.
- Runs Core as its own matrix entry and Cloud as five weight-balanced
breadth shards.
- Fails on any Playwright failure, skip, flaky result, count mismatch,
dirty backend teardown, or stale exact expectation.
- Records visible errors for the page lifetime, so a transient toast
that clears before the assertion still fails S7.
- Reports every coverage exclusion in bold in the GitHub Actions summary
with its mechanism and removal condition.
- Includes two bounded frontend fixes surfaced by the suite: unset
image-upload combos no longer request filename=undefined, and empty
audio-upload sentinels no longer request a preview. These are the only
live src behavior changes in this PR.
- Provides deliberate-break proofs for S1, S2, S3, S9, and S15.

## Tier coverage and applicability

| Tier | Core | Cloud breadth | Assertion |
|---|---|---|---|
| S1 | 6 packs | rows declaring `load` | Every enrolled registered node
instantiates in LiteGraph with exact declared slot materialization. |
| S2 | 6 packs | rows declaring `load` | The enrolled S1 corpus mounts
under Vue Nodes with its visible widgets and slots represented in the
DOM. |
| S3 | 6 packs | rows declaring `load` | Enrolled node identity and
type, initialized live widget topology, and serialized widget values
survive save/reload; exact pinned pack divergences are two-way ledgered.
|
| S4 | retained | retained | Representative type-correct connections
among enrolled nodes survive graph connection and round-trip validation.
|
| S5 | retained | retained | Curated anchor links and one materialized
in-pack link per applicable pack use real drag/connection APIs in both
renderers, excluding only explicitly reported nodes. |
| S6 | retained | retained | Connectivity round-trips reach prompt
conversion and validate the serialized edge contract. |
| S7 | retained, strengthened | retained, strengthened | All
user-visible error surfaces are sampled every animation frame from
initial navigation; transient and final-state errors fail. |
| S8 | retained | retained | Console errors and uncaught page errors are
collected across startup and operations; only exact attributed
signatures are accepted. |
| S9 | all Core `run` rows | VideoHelperSuite, the only Cloud `run` row
| Exact calibrated model-free corpora queue against the real backend and
must execute or produce an observable output. |
| S10 | retained | retained | Manifest shape, exact local node counts,
registered-pack attribution, and collection counts are sentinels. |
| S11 | retained | retained where declared | Expected frontend
extensions and served web-directory assets must register. |
| S12 | Impact case | Impact case | Dynamic list input grows and shrinks
through programmatic and real drag connections in both renderers. |
| S13 | 6 pinned Core profiles | not enrolled | Existing Core
interaction-delta profiles compare at the exact recorded pack refs.
Cloud expansion is
[FE-1659](https://linear.app/comfyorg/issue/FE-1659/define-scalable-s13-interaction-regression-coverage-beyond-core).
|
| S14 | removed | removed | Team-approved removal of full node
geometry/position/size snapshots. |
| S15 | 6 Core curated workflows | not enrolled | Deterministic sink
payload hashes detect valid-but-wrong serialized output. Full-pack
expansion is
[FE-1657](https://linear.app/comfyorg/issue/FE-1657/extend-s15-output-regression-coverage-to-every-custom-node-pack).
|

The suite contains no `test.skip` or `test.fixme`, uses one worker and
`--retries=0`, and independently rejects Playwright-reported skips or
flaky results.

## Explicit coverage debt

- `comfyui-fl-path-animator` does not join the pinned Cloud snapshot.
- LivePortraitKJ has an unfetchable SHA and radiance has an
unsatisfiable `Imath` requirement. Their upstream fixes are tracked by
[FE-1660](https://linear.app/comfyorg/issue/FE-1660/fix-upstream-pack-metadata-and-remove-custom-node-e2e-quarantine).
- SeedVR2 and NVIDIA RTX register zero nodes on a CPU runner. GPU-backed
restoration is
[FE-1658](https://linear.app/comfyorg/issue/FE-1658/add-gpu-backed-custom-node-e2e-coverage-and-remove-cpu-runner).
- `comfyui-itools@0.6.8` is a banned registry artifact whose
`iToolsCropImage` hook has two terminal race outcomes under the same
pin. Only that node is excluded from S1-S8; the pack count remains exact
and its other 21 nodes run. Restoration is
[FE-1675](https://linear.app/comfyorg/issue/FE-1675/e2e-nodes-tests-fix-itools-crop-lifecycle-race-and-restore-s1-s8).
- `VHS_SelectLatest` requires the pack-owned prompt transformation and
is the one model-free node not executed by Cloud S9. Restoration is
[FE-1661](https://linear.app/comfyorg/issue/FE-1661/restore-vhs_selectlatest-s9-execution-coverage).
- `was-node-suite-comfyui/Text Random Prompt` performs an unbounded
public Lexica API request and is excluded only from Core S9.
Deterministic restoration is
[FE-1682](https://linear.app/comfyorg/issue/FE-1682/e2e-nodes-tests-restore-was-text-random-prompt-s9-execution-coverage).
- Exact known pack defects remain exercised under two-way stale ledgers;
they are not skipped. A fixed or changed outcome fails until the
expectation is removed or recalibrated with evidence.

## Review focus

- Whether the two disclosed preview guards are correct and appropriately
scoped; all remaining changes are tests, fixtures, scripts, tooling,
documentation, or CI.
- Whether every tier claim above matches its assertion and
applicability.
- Whether each temporary exclusion is specific, visible, owned, and
removable.
- Whether exact expectation ledgers describe attributable pack behavior
without weakening the asserted contract.
- Whether representative-per-slot connectivity plus per-pack
two-renderer drags is the right bounded surface; this does not claim a
producer-by-consumer cross-product or cross-shard pairing.
- Whether the fixed-shard dependency environment and manifest provenance
are sufficiently deterministic.

Supersedes Comfy-Org#13389 and Comfy-Org#15200. Existing review follow-ups remain tracked
in
[FE-1611](https://linear.app/comfyorg/issue/FE-1611/custom-node-e2e-the-10-review-findings-that-survive-the-cloud-cut).

---------

Co-authored-by: Nathaniel Parson Koroso <tetratrade@zoho.com>
Co-authored-by: IAMtheIAM <iamtheiam@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: CodeJuggernaut <81205671+CodeJuggernaut@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:R3 PR risk grade (advisory shadow check; grader-owned) size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants