Skip to content

fix: keep Preview as Text rendering when the output payload has no text - #14073

Merged
christian-byrne merged 10 commits into
mainfrom
fix/fe-685-previewany-null-safety
Aug 23, 2026
Merged

fix: keep Preview as Text rendering when the output payload has no text#14073
christian-byrne merged 10 commits into
mainfrom
fix/fe-685-previewany-null-safety

Conversation

@christian-byrne

Copy link
Copy Markdown
Contributor

Summary

updateTextPreviewWidgets read message.text off an unvalidated WebSocket payload, so a null message threw and a nulled/absent text array rendered the Preview as Text node blank — the frontend half of the long-running "Preview as Text shows nothing" reports.

Changes

  • What: Normalize the payload before assigning to the preview widget. Nullish becomes an empty string, null entries are filtered out of arrays, and non-string values are stringified instead of blanking the node.

Three failure modes this fixes, all reported against the node:

Payload Before After
null / undefined message TypeError: Cannot read properties of null (reading 'text'), update aborts, stale or empty content renders empty
{"text": [null]} blank node renders empty, and recovers on the next real payload
{"text": ["a", null]} leading blank separator a
{"text": [23.976]} unguarded join 23.976

Review Focus

The {"text": [null]} shape is not hypothetical — Cloud produces exactly that when inference misclassifies a text output as a filename, fails to upload it, and drops it from the payload. That upstream cause is fixed separately in Comfy-Org/cloud#5502; this change is the defensive half, so the node degrades to empty instead of throwing and renders correctly as soon as real text arrives. The two are independently mergeable.

Non-string stringification is deliberately shallow (String(part)) — it preserves today's behavior for objects rather than speculating about a JSON shape the backend does not currently send.

Tests

  • Unit (src/extensions/core/textPreviewWidgets.test.ts): every payload shape above, plus the exact strings users reported blank — LLM JSON in four shapes (compact, pretty-printed, markdown-fenced, array of objects), non-ASCII text, trailing-space prompts, prompts ending in a quoted period, numeric outputs — and the no-preview-widget case. 18 tests pass.
  • E2E (browser_tests/tests/previewAsText.spec.ts): drives those payloads through the WebSocket and asserts the rendered textarea, including that the widget recovers after a null payload and after an output with no text key.

Verified: pnpm typecheck, pnpm typecheck:browser, pnpm lint (0 errors), pnpm format, pnpm knip, pnpm vitest run. The new e2e spec collects under playwright --list but was not executed locally (needs a running backend).

  • Fixes FE-685

🤖 Generated with Claude Code

@christian-byrne
christian-byrne requested a review from a team July 24, 2026 02:08
@dosubot dosubot Bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 67dcbbde-de21-44c1-aa5b-7d024e0ec6e9

📥 Commits

Reviewing files that changed from the base of the PR and between b929cab and ff23ae3.

📒 Files selected for processing (2)
  • browser_tests/tests/previewAsText.spec.ts
  • src/extensions/core/textPreviewWidgets.test.ts

Included review availability: 0 reviews are currently available. Based on recent review activity, included reviews refill at 3 per hour.


📝 Walkthrough

Walkthrough

The preview update API now normalizes nullable, array, numeric, and structured execution output values. Unit and browser tests cover rendering, empty output, null filtering, recovery, and named preview widgets.

Changes

Text preview normalization

Layer / File(s) Summary
Normalize execution output values
src/extensions/core/textPreviewWidgets.ts, src/extensions/core/textPreviewWidgets.test.ts
updateTextPreviewWidgets accepts nullable execution output and normalizes nullish, array, numeric, and other values. Unit tests cover empty output, null filtering, stringification, and missing values.
Validate browser preview rendering
browser_tests/tests/previewAsText.spec.ts
The browser test locates the named preview widget and verifies rendering, empty output, recovery, and varied WebSocket payloads.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🔵 Low · up to ff23a

The change prevents Preview as Text from crashing or rendering blank for null, missing, mixed, and numeric payload values, with broad regression coverage. It is mergeable with owner awareness that an explicit empty-array test is not shown in the supplied current-head evidence.


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
End-To-End Regression Coverage For Fixes ❓ Inconclusive Changed files and description are available, but the PR title and commit subjects are not; the required bug-fix signal cannot be verified under this check. Provide the PR title and commit subjects, then verify whether either contains the required bug-fix language.
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix for Preview as Text rendering with payloads that have no text.
Description check ✅ Passed The description covers the summary, changes, review focus, issue reference, tests, validation results, and applicable limitations.
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.
Website End-To-End Regression Coverage ✅ Passed The changed files are under src/extensions/core and browser_tests; no apps/website/src or apps/website/public runtime file changed, so this check does not apply.
Adr Compliance For Entity/Litegraph Changes ✅ Passed The changed files include a widget utility, so the check applies. The described diff adds payload normalization and tests, with no listed ADR 0003/0008 violation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/fe-685-previewany-null-safety

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

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 08/18/2026, 09:09:33 PM UTC

Links

🎭 Playwright: ✅ 1825 passed, 0 failed · 5 flaky

📊 Browser Reports
  • chromium: View Report (✅ 1804 / ❌ 0 / ⚠️ 5 / ⏭️ 5)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 18 / ❌ 0 / ⚠️ 0 / ⏭️ 0)

📦 Bundle: 8.86 MB gzip 🔴 +152 B

Details

Summary

  • Raw size: 37.4 MB baseline 37.4 MB — 🔴 +116 B
  • Gzip: 8.86 MB baseline 8.85 MB — 🔴 +152 B
  • Brotli: 6.19 MB baseline 6.19 MB — 🔴 +211 B
  • Bundles: 438 current • 438 baseline • 145 added / 145 removed

Category Glance
Other 🔴 +116 B (14.2 MB) · Vendor & Third-Party ⚪ 0 B (16.8 MB) · Data & Services ⚪ 0 B (3.52 MB) · Graph Workspace ⚪ 0 B (1.37 MB) · Panels & Settings ⚪ 0 B (566 kB) · Utilities & Hooks ⚪ 0 B (550 kB) · + 5 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-Cvn_ijhp.js (removed) 3.71 kB 🟢 -3.71 kB 🟢 -1.85 kB 🟢 -1.61 kB
assets/index-D4NnGZoa.js (new) 3.71 kB 🔴 +3.71 kB 🔴 +1.86 kB 🔴 +1.59 kB

Status: 1 added / 1 removed

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

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-BeU-8-vV.js (new) 1.36 MB 🔴 +1.36 MB 🔴 +297 kB 🔴 +223 kB
assets/GraphView-C8UT3YTm.js (removed) 1.36 MB 🟢 -1.36 MB 🟢 -297 kB 🟢 -223 kB
assets/WidgetCompositor-Cfz-w9cE.js (new) 8.17 kB 🔴 +8.17 kB 🔴 +2.76 kB 🔴 +2.48 kB
assets/WidgetCompositor-CVQQ5PMr.js (removed) 8.17 kB 🟢 -8.17 kB 🟢 -2.76 kB 🟢 -2.48 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-C3OyCl_v.js (new) 25 kB 🔴 +25 kB 🔴 +6.24 kB 🔴 +5.52 kB
assets/CloudSurveyView-D7r0AUpF.js (removed) 25 kB 🟢 -25 kB 🟢 -6.24 kB 🟢 -5.52 kB
assets/CloudLayoutView-DWdV3EkV.js (removed) 21.8 kB 🟢 -21.8 kB 🟢 -6.57 kB 🟢 -5.75 kB
assets/CloudLayoutView-WrufaT5M.js (new) 21.8 kB 🔴 +21.8 kB 🔴 +6.57 kB 🔴 +5.74 kB
assets/UserCheckView-DOOGB_tm.js (removed) 8.75 kB 🟢 -8.75 kB 🟢 -2.19 kB 🟢 -1.9 kB
assets/UserCheckView-fka4jDLe.js (new) 8.75 kB 🔴 +8.75 kB 🔴 +2.19 kB 🔴 +1.9 kB
assets/CloudLoginView-BnmOTugR.js (removed) 8.74 kB 🟢 -8.74 kB 🟢 -2.55 kB 🟢 -2.27 kB
assets/CloudLoginView-DfRJVHXP.js (new) 8.74 kB 🔴 +8.74 kB 🔴 +2.55 kB 🔴 +2.27 kB
assets/useCloudAuthPage-DjiEebqL.js (removed) 7.08 kB 🟢 -7.08 kB 🟢 -2.51 kB 🟢 -2.19 kB
assets/useCloudAuthPage-IWnhNsZh.js (new) 7.08 kB 🔴 +7.08 kB 🔴 +2.51 kB 🔴 +2.2 kB
assets/CloudSignupView-B9QFOHyV.js (removed) 6.96 kB 🟢 -6.96 kB 🟢 -2.28 kB 🟢 -2 kB
assets/CloudSignupView-DNsyqI69.js (new) 6.96 kB 🔴 +6.96 kB 🔴 +2.28 kB 🔴 +2 kB
assets/WidgetTextPreview-B_muILm5.js (new) 6.07 kB 🔴 +6.07 kB 🔴 +2.13 kB 🔴 +1.9 kB
assets/WidgetTextPreview-BTl9XGP2.js (removed) 6.07 kB 🟢 -6.07 kB 🟢 -2.13 kB 🟢 -1.9 kB
assets/CloudSubscriptionRedirectView-D-5E4Yvx.js (new) 6.05 kB 🔴 +6.05 kB 🔴 +2.25 kB 🔴 +1.96 kB
assets/CloudSubscriptionRedirectView-RmNPmjq3.js (removed) 6.05 kB 🟢 -6.05 kB 🟢 -2.25 kB 🟢 -1.96 kB
assets/UserSelectView-C4aRW0jo.js (new) 5.49 kB 🔴 +5.49 kB 🔴 +1.96 kB 🔴 +1.71 kB
assets/UserSelectView-CtlsYMQf.js (removed) 5.49 kB 🟢 -5.49 kB 🟢 -1.96 kB 🟢 -1.71 kB
assets/CloudForgotPasswordView-B8MQ16tM.js (new) 4.97 kB 🔴 +4.97 kB 🔴 +1.72 kB 🔴 +1.49 kB
assets/CloudForgotPasswordView-BlgovUW_.js (removed) 4.97 kB 🟢 -4.97 kB 🟢 -1.72 kB 🟢 -1.49 kB
assets/CloudAuthTimeoutView-BRvQ9VSl.js (new) 4.43 kB 🔴 +4.43 kB 🔴 +1.54 kB 🔴 +1.34 kB
assets/CloudAuthTimeoutView-DNPPSp5k.js (removed) 4.43 kB 🟢 -4.43 kB 🟢 -1.54 kB 🟢 -1.34 kB
assets/OAuthLayoutView-DkOcmhxE.js (new) 1.31 kB 🔴 +1.31 kB 🔴 +700 B 🔴 +586 B
assets/OAuthLayoutView-DoiCVwn9.js (removed) 1.31 kB 🟢 -1.31 kB 🟢 -700 B 🟢 -586 B
assets/WidgetTextPreview-3gGmXixr.js (removed) 131 B 🟢 -131 B 🟢 -100 B 🟢 -89 B
assets/WidgetTextPreview-uyC60gS-.js (new) 131 B 🔴 +131 B 🔴 +100 B 🔴 +89 B

Status: 13 added / 13 removed / 4 unchanged

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-BHlMlAN8.js (removed) 49.4 kB 🟢 -49.4 kB 🟢 -9.94 kB 🟢 -8.8 kB
assets/KeybindingPanel-DK5kABRT.js (new) 49.4 kB 🔴 +49.4 kB 🔴 +9.95 kB 🔴 +8.79 kB
assets/SecretsPanel-CMr94R9M.js (new) 33.7 kB 🔴 +33.7 kB 🔴 +7.88 kB 🔴 +6.9 kB
assets/SecretsPanel-DEF5XMjv.js (removed) 33.7 kB 🟢 -33.7 kB 🟢 -7.88 kB 🟢 -6.92 kB
assets/CreditsPanel-CYJs6ncP.js (removed) 11.5 kB 🟢 -11.5 kB 🟢 -3.21 kB 🟢 -2.81 kB
assets/CreditsPanel-Doiq1ovt.js (new) 11.5 kB 🔴 +11.5 kB 🔴 +3.21 kB 🔴 +2.81 kB
assets/AboutPanel-CbSYbygc.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.03 kB 🟢 -2.72 kB
assets/AboutPanel-jhvXAsLy.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.03 kB 🔴 +2.72 kB
assets/ExtensionPanel-DBkwMZLK.js (removed) 9.19 kB 🟢 -9.19 kB 🟢 -2.51 kB 🟢 -2.22 kB
assets/ExtensionPanel-JbB0KyNX.js (new) 9.19 kB 🔴 +9.19 kB 🔴 +2.51 kB 🔴 +2.23 kB
assets/ServerConfigPanel-BaV4gxDJ.js (new) 6.09 kB 🔴 +6.09 kB 🔴 +1.94 kB 🔴 +1.72 kB
assets/ServerConfigPanel-CV8USC4l.js (removed) 6.09 kB 🟢 -6.09 kB 🟢 -1.94 kB 🟢 -1.72 kB
assets/UserPanel-D0pVfS5H.js (new) 5.73 kB 🔴 +5.73 kB 🔴 +1.78 kB 🔴 +1.54 kB
assets/UserPanel-JlAUnPGZ.js (removed) 5.73 kB 🟢 -5.73 kB 🟢 -1.78 kB 🟢 -1.54 kB
assets/refreshRemoteConfig-BPNbzmu9.js (new) 4.24 kB 🔴 +4.24 kB 🔴 +1.51 kB 🔴 +1.34 kB
assets/refreshRemoteConfig-OtypXxM5.js (removed) 4.24 kB 🟢 -4.24 kB 🟢 -1.51 kB 🟢 -1.34 kB
assets/cloudRemoteConfig-CGURxwdT.js (new) 951 B 🔴 +951 B 🔴 +514 B 🔴 +434 B
assets/cloudRemoteConfig-l7tGfExk.js (removed) 951 B 🟢 -951 B 🟢 -514 B 🟢 -422 B
assets/refreshRemoteConfig-D19ef6l9.js (new) 110 B 🔴 +110 B 🔴 +89 B 🔴 +87 B
assets/refreshRemoteConfig-rRnJSSJF.js (removed) 110 B 🟢 -110 B 🟢 -89 B 🟢 -84 B

Status: 10 added / 10 removed / 16 unchanged

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SignUpForm-CMe3I2a6.js (removed) 13.3 kB 🟢 -13.3 kB 🟢 -4.52 kB 🟢 -3.93 kB
assets/SignUpForm-CsH6ydUn.js (new) 13.3 kB 🔴 +13.3 kB 🔴 +4.52 kB 🔴 +3.93 kB
assets/auth-6pkTrkeB.js (new) 3.71 kB 🔴 +3.71 kB 🔴 +1.29 kB 🔴 +1.1 kB
assets/auth-CYSege0c.js (removed) 3.71 kB 🟢 -3.71 kB 🟢 -1.28 kB 🟢 -1.1 kB
assets/UpdatePasswordContent-C8M5EBkz.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -839 B 🟢 -734 B
assets/UpdatePasswordContent-Gz-OD0QI.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +840 B 🔴 +736 B
assets/authStore-c0x3tPnn.js (removed) 128 B 🟢 -128 B 🟢 -104 B 🟢 -105 B
assets/authStore-CWO8iFFM.js (new) 128 B 🔴 +128 B 🔴 +104 B 🔴 +104 B
assets/workspaceAuthStore-DLz0yGao.js (new) 108 B 🔴 +108 B 🔴 +99 B 🔴 +103 B
assets/workspaceAuthStore-DrrtBoQw.js (removed) 108 B 🟢 -108 B 🟢 -99 B 🟢 -104 B
assets/auth-AgyF-vlZ.js (removed) 105 B 🟢 -105 B 🟢 -96 B 🟢 -73 B
assets/auth-BZLEUnej.js (new) 105 B 🔴 +105 B 🔴 +96 B 🔴 +94 B

Status: 6 added / 6 removed / 5 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-IN5Zd5GG.js (new) 90.1 kB 🔴 +90.1 kB 🔴 +19.3 kB 🔴 +16.5 kB
assets/ComfyHubPublishDialog-mLaT13Zl.js (removed) 90.1 kB 🟢 -90.1 kB 🟢 -19.3 kB 🟢 -16.5 kB
assets/useShareDialog-BGYpj6Bk.js (new) 23.9 kB 🔴 +23.9 kB 🔴 +5.69 kB 🔴 +5.04 kB
assets/useShareDialog-DaHWxJcE.js (removed) 23.9 kB 🟢 -23.9 kB 🟢 -5.69 kB 🟢 -5.04 kB
assets/feedbackDialog-BY3MiUR3.js (new) 4.45 kB 🔴 +4.45 kB 🔴 +1.87 kB 🔴 +1.59 kB
assets/feedbackDialog-CqZrb2y2.js (removed) 4.45 kB 🟢 -4.45 kB 🟢 -1.87 kB 🟢 -1.59 kB
assets/useRangeEditor-CsBi0kx-.js (new) 3.29 kB 🔴 +3.29 kB 🔴 +1.14 kB 🔴 +1.05 kB
assets/useRangeEditor-DOUigDCt.js (removed) 3.29 kB 🟢 -3.29 kB 🟢 -1.14 kB 🟢 -1.03 kB
assets/useLayerEditor-B-fU_R2N.js (removed) 1.01 kB 🟢 -1.01 kB 🟢 -490 B 🟢 -409 B
assets/useLayerEditor-DMK_EV3Q.js (new) 1.01 kB 🔴 +1.01 kB 🔴 +492 B 🔴 +405 B
assets/ComfyHubPublishDialog-BDmOaRlS.js (removed) 143 B 🟢 -143 B 🟢 -105 B 🟢 -89 B
assets/ComfyHubPublishDialog-DaHNole5.js (new) 143 B 🔴 +143 B 🔴 +105 B 🔴 +88 B
assets/useSubscriptionDialog-CgXAzPGS.js (removed) 108 B 🟢 -108 B 🟢 -102 B 🟢 -92 B
assets/useSubscriptionDialog-PBRDY6I4.js (new) 108 B 🔴 +108 B 🔴 +102 B 🔴 +90 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-4IGUbGAU.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.97 kB 🔴 +3.53 kB
assets/ComfyQueueButton-CrHhKX8M.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.97 kB 🟢 -3.53 kB
assets/useTerminalTabs-BMLxdC3u.js (new) 11.8 kB 🔴 +11.8 kB 🔴 +3.69 kB 🔴 +3.27 kB
assets/useTerminalTabs-BroiAnj8.js (removed) 11.8 kB 🟢 -11.8 kB 🟢 -3.69 kB 🟢 -3.27 kB
assets/InviteMembersForm-Bd3_92ug.js (new) 8.23 kB 🔴 +8.23 kB 🔴 +2.74 kB 🔴 +2.43 kB
assets/InviteMembersForm-BG0fqrSn.js (removed) 8.23 kB 🟢 -8.23 kB 🟢 -2.74 kB 🟢 -2.43 kB
assets/SubscribeButton-aApa5mJj.js (new) 2.15 kB 🔴 +2.15 kB 🔴 +968 B 🔴 +846 B
assets/SubscribeButton-oV6_9b_a.js (removed) 2.15 kB 🟢 -2.15 kB 🟢 -968 B 🟢 -848 B
assets/cloudFeedbackTopbarButton-DgqD3qi1.js (new) 705 B 🔴 +705 B 🔴 +422 B 🔴 +363 B
assets/cloudFeedbackTopbarButton-LA0B6bXi.js (removed) 705 B 🟢 -705 B 🟢 -419 B 🟢 -360 B
assets/ComfyQueueButton-BNq_4Fs9.js (new) 128 B 🔴 +128 B 🔴 +99 B 🔴 +91 B
assets/ComfyQueueButton-DuNYsfav.js (removed) 128 B 🟢 -128 B 🟢 -99 B 🟢 -91 B

Status: 6 added / 6 removed / 8 unchanged

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

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/settingStore-BaERHezc.js (new) 3.16 MB 🔴 +3.16 MB 🔴 +741 kB 🔴 +557 kB
assets/settingStore-Duy1r7ad.js (removed) 3.16 MB 🟢 -3.16 MB 🟢 -741 kB 🟢 -557 kB
assets/api-4oBbtrRO.js (removed) 186 kB 🟢 -186 kB 🟢 -39.8 kB 🟢 -33.4 kB
assets/api-Beq7JrtA.js (new) 186 kB 🔴 +186 kB 🔴 +39.8 kB 🔴 +33.4 kB
assets/load3dService-BBETvkh4.js (new) 132 kB 🔴 +132 kB 🔴 +29.3 kB 🔴 +24.7 kB
assets/load3dService-Dg23pE1h.js (removed) 132 kB 🟢 -132 kB 🟢 -29.3 kB 🟢 -24.6 kB
assets/workflowShareService-BplYc6Hq.js (removed) 16.5 kB 🟢 -16.5 kB 🟢 -4.91 kB 🟢 -4.35 kB
assets/workflowShareService-BRycjxLc.js (new) 16.5 kB 🔴 +16.5 kB 🔴 +4.91 kB 🔴 +4.34 kB
assets/keybindingService-DbCzyqSh.js (new) 6.89 kB 🔴 +6.89 kB 🔴 +1.73 kB 🔴 +1.49 kB
assets/keybindingService-DttrjwWG.js (removed) 6.89 kB 🟢 -6.89 kB 🟢 -1.73 kB 🟢 -1.49 kB
assets/releaseStore-BoDy403C.js (removed) 6.72 kB 🟢 -6.72 kB 🟢 -2.03 kB 🟢 -1.77 kB
assets/releaseStore-BYmxvD4H.js (new) 6.72 kB 🔴 +6.72 kB 🔴 +2.03 kB 🔴 +1.77 kB
assets/systemStatsStore-CEhwTUkL.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.74 kB 🔴 +1.47 kB
assets/systemStatsStore-CTs11xfP.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.74 kB 🟢 -1.47 kB
assets/userStore-Bq3rcsVn.js (removed) 2.38 kB 🟢 -2.38 kB 🟢 -896 B 🟢 -796 B
assets/userStore-ZOboc3Ua.js (new) 2.38 kB 🔴 +2.38 kB 🔴 +898 B 🔴 +796 B
assets/audioService-u6_RExQO.js (removed) 1.71 kB 🟢 -1.71 kB 🟢 -829 B 🟢 -732 B
assets/audioService-YN8WABU6.js (new) 1.71 kB 🔴 +1.71 kB 🔴 +830 B 🔴 +730 B
assets/dialogService-BfBQf2rF.js (removed) 98 B 🟢 -98 B 🟢 -97 B 🟢 -82 B
assets/dialogService-CFF6IodN.js (new) 98 B 🔴 +98 B 🔴 +97 B 🔴 +86 B
assets/releaseStore-B2r39Og1.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -93 B
assets/releaseStore-qfFInvoH.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +91 B
assets/settingStore-CmJI8Ox3.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -77 B
assets/settingStore-CtO5S2js.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +83 B
assets/assetsStore-CYZWLyQm.js (new) 94 B 🔴 +94 B 🔴 +92 B 🔴 +84 B
assets/assetsStore-tFnhTGga.js (removed) 94 B 🟢 -94 B 🟢 -92 B 🟢 -84 B
assets/api-B0LVscqH.js (removed) 62 B 🟢 -62 B 🟢 -74 B 🟢 -66 B
assets/api-BmptIobz.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-C6s7YuiC.js (removed) 236 kB 🟢 -236 kB 🟢 -53.1 kB 🟢 -43.2 kB
assets/useConflictDetection-CdvXP8j3.js (new) 236 kB 🔴 +236 kB 🔴 +53.1 kB 🔴 +43.2 kB
assets/useLayerEditorSession-Bz1Az15E.js (removed) 158 kB 🟢 -158 kB 🟢 -40.8 kB 🟢 -34.3 kB
assets/useLayerEditorSession-C-H6Pcki.js (new) 158 kB 🔴 +158 kB 🔴 +40.8 kB 🔴 +34.3 kB
assets/useLoad3d-7QQ6S_jW.js (removed) 25.8 kB 🟢 -25.8 kB 🟢 -5.8 kB 🟢 -5.14 kB
assets/useLoad3d-Dq_9dwej.js (new) 25.8 kB 🔴 +25.8 kB 🔴 +5.8 kB 🔴 +5.15 kB
assets/useLoad3dViewer-aYQJu6qb.js (new) 21.2 kB 🔴 +21.2 kB 🔴 +4.98 kB 🔴 +4.37 kB
assets/useLoad3dViewer-B-Sz807w.js (removed) 21.2 kB 🟢 -21.2 kB 🟢 -4.98 kB 🟢 -4.37 kB
assets/useImageCrop-B_DWrLAO.js (removed) 14.9 kB 🟢 -14.9 kB 🟢 -3.42 kB 🟢 -2.98 kB
assets/useImageCrop-uZZZaA6c.js (new) 14.9 kB 🔴 +14.9 kB 🔴 +3.42 kB 🔴 +2.99 kB
assets/useDowngradeToPersonal-g9OC6x8H.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +2.73 kB 🔴 +2.35 kB
assets/useDowngradeToPersonal-VB7c0gHg.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -2.73 kB 🟢 -2.35 kB
assets/useFeatureFlags-BpIeKrKx.js (new) 7.32 kB 🔴 +7.32 kB 🔴 +2.06 kB 🔴 +1.74 kB
assets/useFeatureFlags-CCmCxsLM.js (removed) 7.32 kB 🟢 -7.32 kB 🟢 -2.06 kB 🟢 -1.76 kB
assets/useCompositorLayers-B7T3mDOz.js (removed) 2.94 kB 🟢 -2.94 kB 🟢 -898 B 🟢 -804 B
assets/useCompositorLayers-CqX81dR1.js (new) 2.94 kB 🔴 +2.94 kB 🔴 +897 B 🔴 +803 B
assets/assetPreviewUtil-CQVIeD14.js (new) 2.35 kB 🔴 +2.35 kB 🔴 +966 B 🔴 +846 B
assets/assetPreviewUtil-vnnqohd1.js (removed) 2.35 kB 🟢 -2.35 kB 🟢 -966 B 🟢 -847 B
assets/useUpstreamValue-BFsuPF3e.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +758 B 🔴 +684 B
assets/useUpstreamValue-BSZ3esBp.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -757 B 🟢 -685 B
assets/useWorkspaceTierLabel-BZ3MNigN.js (removed) 1.93 kB 🟢 -1.93 kB 🟢 -813 B 🟢 -696 B
assets/useWorkspaceTierLabel-DZVo9efB.js (new) 1.93 kB 🔴 +1.93 kB 🔴 +812 B 🔴 +698 B
assets/subscriptionCheckoutUtil-CuotArG6.js (new) 877 B 🔴 +877 B 🔴 +521 B 🔴 +438 B
assets/subscriptionCheckoutUtil-DVDaeofr.js (removed) 877 B 🟢 -877 B 🟢 -521 B 🟢 -432 B
assets/useSessionCookie-C9NNRDrp.js (removed) 652 B 🟢 -652 B 🟢 -335 B 🟢 -290 B
assets/useSessionCookie-sW1HH7GA.js (new) 652 B 🔴 +652 B 🔴 +337 B 🔴 +291 B
assets/useLoad3d-DcR9J--o.js (removed) 311 B 🟢 -311 B 🟢 -165 B 🟢 -153 B
assets/useLoad3d-GXbAc4ZF.js (new) 311 B 🔴 +311 B 🔴 +163 B 🔴 +147 B
assets/useSessionCookie-61Vygua8.js (new) 101 B 🔴 +101 B 🔴 +86 B 🔴 +77 B
assets/useSessionCookie-B4ZNMlTh.js (removed) 101 B 🟢 -101 B 🟢 -86 B 🟢 -79 B
assets/useFeatureFlags-C9p8wj01.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +83 B
assets/useFeatureFlags-CIllEnMf.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -81 B
assets/useLoad3dViewer-Br4zbl82.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -87 B
assets/useLoad3dViewer-GySGUfLf.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +83 B
assets/useCurrentUser-B018vQxS.js (new) 94 B 🔴 +94 B 🔴 +95 B 🔴 +87 B
assets/useCurrentUser-CQkuqbYx.js (removed) 94 B 🟢 -94 B 🟢 -95 B 🟢 -85 B

Status: 18 added / 18 removed / 20 unchanged

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

External libraries and shared vendor chunks

Status: 18 unchanged

Other — 14.2 MB (baseline 14.2 MB) • 🔴 +116 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-lESGl2i9.js (new) 115 kB 🔴 +115 kB 🔴 +29.7 kB 🔴 +25.1 kB
assets/core-DOE5rXuC.js (removed) 115 kB 🟢 -115 kB 🟢 -29.7 kB 🟢 -25.1 kB
assets/WidgetSelect-Cc2cJosD.js (removed) 88.8 kB 🟢 -88.8 kB 🟢 -20.1 kB 🟢 -17.2 kB
assets/WidgetSelect-CiXtXQuj.js (new) 88.8 kB 🔴 +88.8 kB 🔴 +20.1 kB 🔴 +17.2 kB
assets/SubscriptionPanelContentWorkspace-Beh1x2YP.js (new) 81.2 kB 🔴 +81.2 kB 🔴 +16.1 kB 🔴 +13.9 kB
assets/SubscriptionPanelContentWorkspace-DshDYi-V.js (removed) 81.2 kB 🟢 -81.2 kB 🟢 -16.1 kB 🟢 -13.8 kB
assets/Load3D-BCjvSS7n.js (removed) 73.3 kB 🟢 -73.3 kB 🟢 -12.1 kB 🟢 -10.3 kB
assets/Load3D-DPfY4e46.js (new) 73.3 kB 🔴 +73.3 kB 🔴 +12.1 kB 🔴 +10.3 kB
assets/WidgetVideoEdit-m3VYbHrq.js (removed) 67.5 kB 🟢 -67.5 kB 🟢 -15.7 kB 🟢 -13.9 kB
assets/WidgetVideoEdit-ZX7vmbx3.js (new) 67.5 kB 🔴 +67.5 kB 🔴 +15.7 kB 🔴 +13.9 kB
assets/SubscriptionTransitionPreviewWorkspace-BEMkfdUx.js (new) 66.6 kB 🔴 +66.6 kB 🔴 +13.4 kB 🔴 +11.7 kB
assets/SubscriptionTransitionPreviewWorkspace-BlQXGtia.js (removed) 66.6 kB 🟢 -66.6 kB 🟢 -13.4 kB 🟢 -11.7 kB
assets/WorkspaceSettingsPanelContent-CObEc3M-.js (new) 58.2 kB 🔴 +58.2 kB 🔴 +12.4 kB 🔴 +10.8 kB
assets/WorkspaceSettingsPanelContent-CXN9-CMO.js (removed) 58.2 kB 🟢 -58.2 kB 🟢 -12.4 kB 🟢 -10.8 kB
assets/Preview3d-1nvO2wGL.js (removed) 50.9 kB 🟢 -50.9 kB 🟢 -8.32 kB 🟢 -7.25 kB
assets/Preview3d-DyVuPYHy.js (new) 50.9 kB 🔴 +50.9 kB 🔴 +8.32 kB 🔴 +7.25 kB
assets/main-Bb_fuols.js (removed) 45.3 kB 🟢 -45.3 kB 🟢 -13.2 kB 🟢 -11.4 kB
assets/main-BVhhm0xs.js (new) 45.3 kB 🔴 +45.3 kB 🔴 +13.2 kB 🔴 +11.4 kB
assets/SubscriptionRequiredDialogContentUnified-CGlqcN4r.js (removed) 42.7 kB 🟢 -42.7 kB 🟢 -9.39 kB 🟢 -8.19 kB
assets/SubscriptionRequiredDialogContentUnified-D5qxXJ6N.js (new) 42.7 kB 🔴 +42.7 kB 🔴 +9.39 kB 🔴 +8.18 kB
assets/LayerEditorContent-VupsE4_T.js (removed) 42.5 kB 🟢 -42.5 kB 🟢 -9.62 kB 🟢 -8.44 kB
assets/LayerEditorContent-WRjdXjO-.js (new) 42.5 kB 🔴 +42.5 kB 🔴 +9.62 kB 🔴 +8.43 kB
assets/WidgetBoundingBoxes-CUhpYjce.js (new) 33.7 kB 🔴 +33.7 kB 🔴 +9.16 kB 🔴 +8.13 kB
assets/WidgetBoundingBoxes-DjukyX4h.js (removed) 33.7 kB 🟢 -33.7 kB 🟢 -9.16 kB 🟢 -8.11 kB
assets/WidgetPainter-CckM39s0.js (removed) 32.6 kB 🟢 -32.6 kB 🟢 -7.87 kB 🟢 -6.97 kB
assets/WidgetPainter-DKEzUhXo.js (new) 32.6 kB 🔴 +32.6 kB 🔴 +7.87 kB 🔴 +6.97 kB
assets/Load3dViewerContent-CejmwD2Y.js (new) 30.8 kB 🔴 +30.8 kB 🔴 +6.29 kB 🔴 +5.45 kB
assets/Load3dViewerContent-CnfvLkuM.js (removed) 30.8 kB 🟢 -30.8 kB 🟢 -6.28 kB 🟢 -5.45 kB
assets/SubscriptionRequiredDialogContent-3HQnnnk3.js (new) 26.9 kB 🔴 +26.9 kB 🔴 +6.36 kB 🔴 +5.6 kB
assets/SubscriptionRequiredDialogContent-DxRvd7TR.js (removed) 26.9 kB 🟢 -26.9 kB 🟢 -6.35 kB 🟢 -5.6 kB
assets/SubscriptionRequiredDialogContentWorkspace-B5IxjQuB.js (new) 25.1 kB 🔴 +25.1 kB 🔴 +5.81 kB 🔴 +5.13 kB
assets/SubscriptionRequiredDialogContentWorkspace-Bq3csZtX.js (removed) 25.1 kB 🟢 -25.1 kB 🟢 -5.81 kB 🟢 -5.11 kB
assets/CreditsTile-7eAFxI8y.js (new) 24.9 kB 🔴 +24.9 kB 🔴 +6.65 kB 🔴 +5.84 kB
assets/CreditsTile-ng-ks9Cn.js (removed) 24.9 kB 🟢 -24.9 kB 🟢 -6.65 kB 🟢 -5.84 kB
assets/load3d-aSPzr-Nh.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.4 kB 🔴 +4.68 kB
assets/load3d-DAKuzOl1.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.4 kB 🟢 -4.68 kB
assets/CurrentUserPopoverWorkspace-C2gin2R6.js (new) 21.8 kB 🔴 +21.8 kB 🔴 +4.94 kB 🔴 +4.4 kB
assets/CurrentUserPopoverWorkspace-NhQFbSVw.js (removed) 21.8 kB 🟢 -21.8 kB 🟢 -4.94 kB 🟢 -4.4 kB
assets/SignInContent-B5N9X7cD.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5.18 kB 🔴 +4.54 kB
assets/SignInContent-B8AZxgbo.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -5.18 kB 🟢 -4.54 kB
assets/WidgetRecordAudio-Bq9rkxBU.js (new) 16.6 kB 🔴 +16.6 kB 🔴 +4.6 kB 🔴 +4.1 kB
assets/WidgetRecordAudio-CmcoedpR.js (removed) 16.6 kB 🟢 -16.6 kB 🟢 -4.6 kB 🟢 -4.1 kB
assets/WidgetInputNumber-CBF5--rW.js (new) 13.9 kB 🔴 +13.9 kB 🔴 +3.63 kB 🔴 +3.22 kB
assets/WidgetInputNumber-XCjuic5x.js (removed) 13.9 kB 🟢 -13.9 kB 🟢 -3.63 kB 🟢 -3.21 kB
assets/WidgetRange-GVVgDQcr.js (new) 13.7 kB 🔴 +13.7 kB 🔴 +3.55 kB 🔴 +3.12 kB
assets/WidgetRange-pX5hnO92.js (removed) 13.7 kB 🟢 -13.7 kB 🟢 -3.55 kB 🟢 -3.13 kB
assets/WaveAudioPlayer-BI1ggH_7.js (new) 12.8 kB 🔴 +12.8 kB 🔴 +3.46 kB 🔴 +3.04 kB
assets/WaveAudioPlayer-DiXjxPsf.js (removed) 12.8 kB 🟢 -12.8 kB 🟢 -3.46 kB 🟢 -3.05 kB
assets/WidgetCurve-C_OjnQ8U.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.48 kB 🔴 +3.15 kB
assets/WidgetCurve-C4JMJG1c.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.47 kB 🟢 -3.15 kB
assets/TeamWorkspacesDialogContent-CZof_v5s.js (new) 10.3 kB 🔴 +10.3 kB 🔴 +2.97 kB 🔴 +2.63 kB
assets/TeamWorkspacesDialogContent-mmKOPEcG.js (removed) 10.3 kB 🟢 -10.3 kB 🟢 -2.97 kB 🟢 -2.63 kB
assets/onboardingCloudRoutes-2EShklXB.js (removed) 9.46 kB 🟢 -9.46 kB 🟢 -2.84 kB 🟢 -2.44 kB
assets/onboardingCloudRoutes-DEpbsH-9.js (new) 9.46 kB 🔴 +9.46 kB 🔴 +2.85 kB 🔴 +2.44 kB
assets/Load3DConfiguration-DHFCMkNp.js (removed) 8.91 kB 🟢 -8.91 kB 🟢 -2.61 kB 🟢 -2.3 kB
assets/Load3DConfiguration-DNftE8DU.js (new) 8.91 kB 🔴 +8.91 kB 🔴 +2.61 kB 🔴 +2.31 kB
assets/WidgetImageCrop-COQBCngn.js (new) 8.49 kB 🔴 +8.49 kB 🔴 +2.66 kB 🔴 +2.35 kB
assets/WidgetImageCrop-DgHBager.js (removed) 8.49 kB 🟢 -8.49 kB 🟢 -2.66 kB 🟢 -2.36 kB
assets/SetMemberCreditLimitDialogContent-B1tXV2KX.js (removed) 8.47 kB 🟢 -8.47 kB 🟢 -2.34 kB 🟢 -2.04 kB
assets/SetMemberCreditLimitDialogContent-DKmpK7sO.js (new) 8.47 kB 🔴 +8.47 kB 🔴 +2.34 kB 🔴 +2.05 kB
assets/nodeTemplates-B3z6UroC.js (removed) 8.32 kB 🟢 -8.32 kB 🟢 -2.85 kB 🟢 -2.51 kB
assets/nodeTemplates-B9s9S_cU.js (new) 8.32 kB 🔴 +8.32 kB 🔴 +2.85 kB 🔴 +2.5 kB
assets/NightlySurveyController-DIPi71YI.js (new) 7.5 kB 🔴 +7.5 kB 🔴 +2.55 kB 🔴 +2.25 kB
assets/NightlySurveyController-Dzsu7ts-.js (removed) 7.5 kB 🟢 -7.5 kB 🟢 -2.55 kB 🟢 -2.25 kB
assets/CloudRunButtonWrapper-CuL85OyU.js (removed) 7.29 kB 🟢 -7.29 kB 🟢 -2.29 kB 🟢 -2 kB
assets/CloudRunButtonWrapper-DIvM9NZI.js (new) 7.29 kB 🔴 +7.29 kB 🔴 +2.29 kB 🔴 +2.01 kB
assets/WidgetWithControl-CzZmFiKD.js (new) 6.48 kB 🔴 +6.48 kB 🔴 +2.65 kB 🔴 +2.32 kB
assets/WidgetWithControl-gQpQuus2.js (removed) 6.48 kB 🟢 -6.48 kB 🟢 -2.64 kB 🟢 -2.31 kB
assets/missingModelMetadata-B7DLbbqb.js (removed) 6.45 kB 🟢 -6.45 kB 🟢 -2.21 kB 🟢 -1.93 kB
assets/missingModelMetadata-D4wKfSeX.js (new) 6.45 kB 🔴 +6.45 kB 🔴 +2.2 kB 🔴 +1.93 kB
assets/CancelSubscriptionDialogContent-B8S9pe6g.js (new) 5.97 kB 🔴 +5.97 kB 🔴 +1.98 kB 🔴 +1.75 kB
assets/CancelSubscriptionDialogContent-CVAOE2De.js (removed) 5.97 kB 🟢 -5.97 kB 🟢 -1.98 kB 🟢 -1.75 kB
assets/load3dPreviewExtensions-DqDL-ytl.js (new) 5.88 kB 🔴 +5.88 kB 🔴 +1.81 kB 🔴 +1.6 kB
assets/load3dPreviewExtensions-DQqW1nyM.js (removed) 5.88 kB 🟢 -5.88 kB 🟢 -1.81 kB 🟢 -1.6 kB
assets/launchCancellationFlow-BC93bXeU.js (removed) 5.18 kB 🟢 -5.18 kB 🟢 -1.78 kB 🟢 -1.55 kB
assets/launchCancellationFlow-DH83fqs1.js (new) 5.18 kB 🔴 +5.18 kB 🔴 +1.78 kB 🔴 +1.56 kB
assets/CreateWorkspaceDialogContent-CIwcb0s4.js (removed) 5.12 kB 🟢 -5.12 kB 🟢 -1.79 kB 🟢 -1.54 kB
assets/CreateWorkspaceDialogContent-lYQEF1vW.js (new) 5.12 kB 🔴 +5.12 kB 🔴 +1.79 kB 🔴 +1.55 kB
assets/ChangeMemberRoleDialogContent-CbSrkgjT.js (removed) 4.97 kB 🟢 -4.97 kB 🟢 -1.64 kB 🟢 -1.43 kB
assets/ChangeMemberRoleDialogContent-CuXb7gRq.js (new) 4.97 kB 🔴 +4.97 kB 🔴 +1.64 kB 🔴 +1.43 kB
assets/InviteMemberDialogContent-DO7ydGCk.js (removed) 4.96 kB 🟢 -4.96 kB 🟢 -1.64 kB 🟢 -1.44 kB
assets/InviteMemberDialogContent-GbCbofCy.js (new) 4.96 kB 🔴 +4.96 kB 🔴 +1.64 kB 🔴 +1.44 kB
assets/EditWorkspaceDialogContent-BlNDmphe.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.76 kB 🟢 -1.53 kB
assets/EditWorkspaceDialogContent-BnwZITtq.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.76 kB 🔴 +1.53 kB
assets/WidgetTextarea-BNZ9SwVB.js (removed) 4.83 kB 🟢 -4.83 kB 🟢 -1.88 kB 🟢 -1.65 kB
assets/WidgetTextarea-BzsAL3Zn.js (new) 4.83 kB 🔴 +4.83 kB 🔴 +1.88 kB 🔴 +1.64 kB
assets/saveMesh-77theSST.js (new) 4.76 kB 🔴 +4.76 kB 🔴 +1.52 kB 🔴 +1.34 kB
assets/saveMesh-DBgBngG5.js (removed) 4.76 kB 🟢 -4.76 kB 🟢 -1.52 kB 🟢 -1.34 kB
assets/WorkspacePanelContent-C0gnm-wd.js (new) 4.74 kB 🔴 +4.74 kB 🔴 +1.63 kB 🔴 +1.44 kB
assets/WorkspacePanelContent-D1BhMtdE.js (removed) 4.74 kB 🟢 -4.74 kB 🟢 -1.63 kB 🟢 -1.44 kB
assets/ValueControlPopover-B620fE_O.js (new) 4.49 kB 🔴 +4.49 kB 🔴 +1.55 kB 🔴 +1.38 kB
assets/ValueControlPopover-D32euKu1.js (removed) 4.49 kB 🟢 -4.49 kB 🟢 -1.55 kB 🟢 -1.38 kB
assets/DeleteWorkspaceDialogContent-BZVRU9wj.js (new) 3.84 kB 🔴 +3.84 kB 🔴 +1.44 kB 🔴 +1.24 kB
assets/DeleteWorkspaceDialogContent-CtsG2Ibg.js (removed) 3.84 kB 🟢 -3.84 kB 🟢 -1.44 kB 🟢 -1.24 kB
assets/RemoveMemberDialogContent-CFFk93BE.js (removed) 3.76 kB 🟢 -3.76 kB 🟢 -1.38 kB 🟢 -1.2 kB
assets/RemoveMemberDialogContent-Ch5XA9-O.js (new) 3.76 kB 🔴 +3.76 kB 🔴 +1.38 kB 🔴 +1.2 kB
assets/RevokeInviteDialogContent-B46mMMav.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.39 kB 🟢 -1.21 kB
assets/RevokeInviteDialogContent-BBIVgVJb.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.39 kB 🔴 +1.21 kB
assets/LeaveWorkspaceDialogContent-BZLhLacp.js (new) 3.67 kB 🔴 +3.67 kB 🔴 +1.38 kB 🔴 +1.19 kB
assets/LeaveWorkspaceDialogContent-CBcXaJfX.js (removed) 3.67 kB 🟢 -3.67 kB 🟢 -1.38 kB 🟢 -1.19 kB
assets/InviteMemberUpsellDialogContent-BqGEhw0n.js (new) 3.47 kB 🔴 +3.47 kB 🔴 +1.24 kB 🔴 +1.07 kB
assets/InviteMemberUpsellDialogContent-eoPgi-8f.js (removed) 3.47 kB 🟢 -3.47 kB 🟢 -1.24 kB 🟢 -1.09 kB
assets/workspaceCheckoutTelemetry-BkU8XwAn.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.52 kB 🔴 +1.32 kB
assets/workspaceCheckoutTelemetry-DeURQ0Hf.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.52 kB 🟢 -1.32 kB
assets/GlobalToast-BJLrdF-v.js (new) 3.25 kB 🔴 +3.25 kB 🔴 +1.3 kB 🔴 +1.11 kB
assets/GlobalToast-Dc7FeQkK.js (removed) 3.25 kB 🟢 -3.25 kB 🟢 -1.3 kB 🟢 -1.11 kB
assets/Media3DTop-BciWO8_g.js (new) 3.21 kB 🔴 +3.21 kB 🔴 +1.27 kB 🔴 +1.11 kB
assets/Media3DTop-XWnsl9Ny.js (removed) 3.21 kB 🟢 -3.21 kB 🟢 -1.26 kB 🟢 -1.1 kB
assets/load3dAdvanced-C_T5D3dJ.js (new) 2.82 kB 🔴 +2.82 kB 🔴 +1.1 kB 🔴 +956 B
assets/load3dAdvanced-CROgtdXK.js (removed) 2.82 kB 🟢 -2.82 kB 🟢 -1.09 kB 🟢 -956 B
assets/SubscribeToRun-BdPPff4k.js (new) 2.39 kB 🔴 +2.39 kB 🔴 +1.03 kB 🔴 +910 B
assets/SubscribeToRun-DmLoJnD2.js (removed) 2.39 kB 🟢 -2.39 kB 🟢 -1.03 kB 🟢 -905 B
assets/MediaAudioTop-C6z8TkYN.js (removed) 1.62 kB 🟢 -1.62 kB 🟢 -807 B 🟢 -668 B
assets/MediaAudioTop-DhH2kbwF.js (new) 1.62 kB 🔴 +1.62 kB 🔴 +807 B 🔴 +668 B
assets/cloudSessionCookie-BGDngzvk.js (new) 933 B 🔴 +933 B 🔴 +432 B 🔴 +377 B
assets/cloudSessionCookie-Ddf-ceMZ.js (removed) 933 B 🟢 -933 B 🟢 -431 B 🟢 -375 B
assets/cloudBadges-B_LbcB36.js (new) 922 B 🔴 +922 B 🔴 +514 B 🔴 +442 B
assets/cloudBadges-DFo-eUEd.js (removed) 922 B 🟢 -922 B 🟢 -513 B 🟢 -442 B
assets/Load3DAdvanced-BRLqfi9w.js (removed) 761 B 🟢 -761 B 🟢 -423 B 🟢 -358 B
assets/Load3DAdvanced-DGHK9S2U.js (new) 761 B 🔴 +761 B 🔴 +423 B 🔴 +361 B
assets/nightlyBadges-B9mlA98K.js (removed) 411 B 🟢 -411 B 🟢 -272 B 🟢 -230 B
assets/nightlyBadges-BCeoskJf.js (new) 411 B 🔴 +411 B 🔴 +273 B 🔴 +270 B
assets/Load3dViewerContent-CjvgaxDi.js (removed) 137 B 🟢 -137 B 🟢 -103 B 🟢 -91 B
assets/Load3dViewerContent-Cn3RYUJn.js (new) 137 B 🔴 +137 B 🔴 +103 B 🔴 +86 B
assets/missingModelMetadata-BKxr__b_.js (new) 125 B 🔴 +125 B 🔴 +103 B 🔴 +110 B
assets/missingModelMetadata-C_YMFt2g.js (removed) 125 B 🟢 -125 B 🟢 -103 B 🟢 -102 B
assets/Load3DAdvanced-CUSWSgXQ.js (new) 122 B 🔴 +122 B 🔴 +97 B 🔴 +88 B
assets/Load3DAdvanced-DFxZLL9E.js (removed) 122 B 🟢 -122 B 🟢 -97 B 🟢 -89 B
assets/WidgetLegacy-BV4p8AeS.js (removed) 117 B 🟢 -117 B 🟢 -106 B 🟢 -92 B
assets/WidgetLegacy-p5Kq0zZl.js (new) 117 B 🔴 +117 B 🔴 +106 B 🔴 +96 B
assets/workflowDraftStoreV2-BmRj-fIT.js (removed) 112 B 🟢 -112 B 🟢 -101 B 🟢 -110 B
assets/workflowDraftStoreV2-BYQvHgdS.js (new) 112 B 🔴 +112 B 🔴 +101 B 🔴 +110 B
assets/Load3D-bMPCx6F5.js (new) 98 B 🔴 +98 B 🔴 +89 B 🔴 +81 B
assets/Load3D-BX-ysKzE.js (removed) 98 B 🟢 -98 B 🟢 -89 B 🟢 -86 B
assets/changeTracker-BkLU_l4Q.js (removed) 91 B 🟢 -91 B 🟢 -93 B 🟢 -85 B
assets/changeTracker-Cu8CbuUS.js (new) 91 B 🔴 +91 B 🔴 +93 B 🔴 +88 B

Status: 68 added / 68 removed / 217 unchanged

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 56.5 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 64.3 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 60.3 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 67.7 MB heap
large-graph-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 73.2 MB heap
large-graph-pan: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 78.0 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 73.5 MB heap
legacy-node-drag: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 60.5 MB heap
minimap-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 73.1 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.0 MB heap
subgraph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 63.0 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 56.1 MB heap
subgraph-transition-enter: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 138ms TBT · 95.0 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 79.0 MB heap
vue-large-graph-idle: · 56.3 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 170.0 MB heap
vue-large-graph-pan: · 54.6 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 132ms TBT · 175.4 MB heap
workflow-execution: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 61.5 MB heap

⚠️ 3 regressions detected

Show regressions
Metric Baseline PR (median) Δ Sig
canvas-idle: task duration 489ms 565ms +16% ⚠️ z=5.5
subgraph-idle: task duration 475ms 497ms +5% ⚠️ z=4.1
workflow-execution: event listeners 99 62 -37% ⚠️ z=2.3
All metrics
Metric Baseline PR (median) Δ Sig
canvas-idle: avg frame time 17ms 17ms +0% z=-0.1
canvas-idle: p95 frame time 17ms 17ms +0%
canvas-idle: layout duration 0ms 0ms +0%
canvas-idle: style recalc duration 7ms 7ms +10% z=-3.8
canvas-idle: layout count 0 0 +0%
canvas-idle: style recalc count 9 8 -17% z=-6.5
canvas-idle: task duration 489ms 565ms +16% ⚠️ z=5.5
canvas-idle: script duration 7ms 8ms +24% z=-7.6
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: heap used 57.1 MB 56.5 MB -1%
canvas-idle: DOM nodes -282 -283 +0% z=-239.6
canvas-idle: event listeners -153 -150 -2% z=-34.1
canvas-mouse-sweep: avg frame time 17ms 17ms -0% z=-1.5
canvas-mouse-sweep: p95 frame time 17ms 17ms +0%
canvas-mouse-sweep: layout duration 3ms 3ms -2% z=-1.1
canvas-mouse-sweep: style recalc duration 33ms 37ms +11% z=-1.9
canvas-mouse-sweep: layout count 12 12 +0%
canvas-mouse-sweep: style recalc count 72 72 +0% z=-2.8
canvas-mouse-sweep: task duration 849ms 872ms +3% z=0.1
canvas-mouse-sweep: script duration 100ms 108ms +8% z=-4.2
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: heap used 59.2 MB 64.3 MB +9%
canvas-mouse-sweep: DOM nodes -282 -283 +0% z=-133.3
canvas-mouse-sweep: event listeners -151 -166 +10% z=-41.8
canvas-zoom-sweep: avg frame time 17ms 17ms +0% z=0.5
canvas-zoom-sweep: p95 frame time 17ms 17ms +0%
canvas-zoom-sweep: layout duration 1ms 1ms -8% z=0.4
canvas-zoom-sweep: style recalc duration 16ms 17ms +7% z=-1.3
canvas-zoom-sweep: layout count 6 6 +0%
canvas-zoom-sweep: style recalc count 32 32 -2% z=0.5
canvas-zoom-sweep: task duration 350ms 356ms +2% z=1.3
canvas-zoom-sweep: script duration 10ms 10ms -1% z=-5.7
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: heap used 59.9 MB 60.3 MB +1%
canvas-zoom-sweep: DOM nodes 76 77 +1% z=-2.8
canvas-zoom-sweep: event listeners 19 19 +0% z=-0.9
dom-widget-clipping: avg frame time 17ms 17ms -0% z=-0.2
dom-widget-clipping: p95 frame time 17ms 17ms -0%
dom-widget-clipping: layout duration 0ms 0ms +0%
dom-widget-clipping: style recalc duration 8ms 7ms -11% z=-3.8
dom-widget-clipping: layout count 0 0 +0%
dom-widget-clipping: style recalc count 11 11 -5% z=-5.2
dom-widget-clipping: task duration 348ms 354ms +2% z=-0.7
dom-widget-clipping: script duration 51ms 52ms +2% z=-4.8
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: heap used 67.5 MB 67.7 MB +0%
dom-widget-clipping: DOM nodes 18 17 -6% z=-3.6
dom-widget-clipping: event listeners 2 2 +0% variance too high
large-graph-idle: avg frame time 17ms 17ms +0% z=-0.2
large-graph-idle: p95 frame time 17ms 17ms -0%
large-graph-idle: layout duration 0ms 0ms +0%
large-graph-idle: style recalc duration 7ms 8ms +20% z=-4.2
large-graph-idle: layout count 0 0 +0%
large-graph-idle: style recalc count 8 9 +6% z=-9.9
large-graph-idle: task duration 600ms 614ms +2% z=1.3
large-graph-idle: script duration 14ms 16ms +14% z=-8.2
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: heap used 72.7 MB 73.2 MB +1%
large-graph-idle: DOM nodes -265 -262 -1% z=-316.6
large-graph-idle: event listeners -151 -149 -1% z=-28.7
large-graph-pan: avg frame time 17ms 17ms +0% z=0.3
large-graph-pan: p95 frame time 17ms 17ms +0%
large-graph-pan: layout duration 0ms 0ms +0%
large-graph-pan: style recalc duration 11ms 13ms +18% z=-5.0
large-graph-pan: layout count 0 0 +0%
large-graph-pan: style recalc count 67 67 -1% z=-4.8
large-graph-pan: task duration 1105ms 1139ms +3% z=1.3
large-graph-pan: script duration 301ms 310ms +3% z=-4.9
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: heap used 73.2 MB 78.0 MB +7%
large-graph-pan: DOM nodes -269 -278 +3% z=-180.0
large-graph-pan: event listeners 4 -88 -2288% z=-111.6
large-graph-zoom: avg frame time 17ms 17ms -0%
large-graph-zoom: p95 frame time 17ms 17ms -0%
large-graph-zoom: layout duration 8ms 8ms +3%
large-graph-zoom: style recalc duration 14ms 14ms -3%
large-graph-zoom: layout count 60 60 +0%
large-graph-zoom: style recalc count 65 64 -2%
large-graph-zoom: task duration 1366ms 1361ms -0%
large-graph-zoom: script duration 377ms 390ms +3%
large-graph-zoom: TBT 0ms 0ms +0%
large-graph-zoom: heap used 72.9 MB 73.5 MB +1%
large-graph-zoom: DOM nodes 6 -273 -4642%
large-graph-zoom: event listeners 8 -70 -969%
legacy-node-drag: avg frame time 17ms 17ms -0%
legacy-node-drag: p95 frame time 17ms 17ms -1%
legacy-node-drag: layout duration 0ms 0ms +0%
legacy-node-drag: style recalc duration 9ms 15ms +71%
legacy-node-drag: layout count 0 0 +0%
legacy-node-drag: style recalc count 45 46 +1%
legacy-node-drag: task duration 1436ms 1495ms +4%
legacy-node-drag: script duration 453ms 480ms +6%
legacy-node-drag: TBT 0ms 0ms +0%
legacy-node-drag: heap used 60.5 MB 60.5 MB -0%
legacy-node-drag: DOM nodes -9 -252 +2694%
legacy-node-drag: event listeners 186 33 -82%
minimap-idle: avg frame time 17ms 17ms +0% z=0.1
minimap-idle: p95 frame time 17ms 17ms +0%
minimap-idle: layout duration 0ms 0ms +0%
minimap-idle: style recalc duration 7ms 8ms +11% z=-2.1
minimap-idle: layout count 0 0 +0%
minimap-idle: style recalc count 7 8 +7% z=-3.0
minimap-idle: task duration 688ms 605ms -12% z=1.6
minimap-idle: script duration 17ms 19ms +10% z=-8.0
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: heap used 71.7 MB 73.1 MB +2%
minimap-idle: DOM nodes -270 -268 -1% z=-209.6
minimap-idle: event listeners -147 -147 +0% z=-229.6
subgraph-dom-widget-clipping: avg frame time 17ms 17ms +0% z=-0.9
subgraph-dom-widget-clipping: p95 frame time 17ms 17ms +1%
subgraph-dom-widget-clipping: layout duration 0ms 0ms +0%
subgraph-dom-widget-clipping: style recalc duration 10ms 10ms +1% z=-2.4
subgraph-dom-widget-clipping: layout count 0 0 +0%
subgraph-dom-widget-clipping: style recalc count 46 47 +1% z=-2.5
subgraph-dom-widget-clipping: task duration 387ms 385ms -0% z=0.4
subgraph-dom-widget-clipping: script duration 122ms 119ms -2% z=-1.4
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: heap used 68.5 MB 69.0 MB +1%
subgraph-dom-widget-clipping: DOM nodes 18 19 +6% z=-2.8
subgraph-dom-widget-clipping: event listeners 8 8 +0% z=-1.4
subgraph-idle: avg frame time 17ms 17ms +0% z=0.4
subgraph-idle: p95 frame time 17ms 17ms -1%
subgraph-idle: layout duration 0ms 0ms +0%
subgraph-idle: style recalc duration 7ms 8ms +18% z=-2.6
subgraph-idle: layout count 0 0 +0%
subgraph-idle: style recalc count 9 9 +0% z=-2.9
subgraph-idle: task duration 475ms 497ms +5% ⚠️ z=4.1
subgraph-idle: script duration 6ms 7ms +19% z=-4.8
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: heap used 68.5 MB 63.0 MB -8%
subgraph-idle: DOM nodes -280 -280 +0% z=-201.9
subgraph-idle: event listeners -181 -166 -8% variance too high
subgraph-mouse-sweep: avg frame time 17ms 17ms +0% z=0.8
subgraph-mouse-sweep: p95 frame time 17ms 17ms +0%
subgraph-mouse-sweep: layout duration 4ms 5ms +4% z=-0.1
subgraph-mouse-sweep: style recalc duration 34ms 36ms +6% z=-1.9
subgraph-mouse-sweep: layout count 16 16 +0%
subgraph-mouse-sweep: style recalc count 75 75 +0% z=-2.7
subgraph-mouse-sweep: task duration 741ms 801ms +8% z=0.5
subgraph-mouse-sweep: script duration 76ms 86ms +13% z=-2.3
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: heap used 57.7 MB 56.1 MB -3%
subgraph-mouse-sweep: DOM nodes -279 -281 +1% z=-155.5
subgraph-mouse-sweep: event listeners -151 -151 +0% variance too high
subgraph-transition-enter: avg frame time 17ms 17ms -0%
subgraph-transition-enter: p95 frame time 17ms 17ms -1%
subgraph-transition-enter: layout duration 13ms 13ms +2%
subgraph-transition-enter: style recalc duration 30ms 31ms +4%
subgraph-transition-enter: layout count 13 13 +0%
subgraph-transition-enter: style recalc count 18 18 +0%
subgraph-transition-enter: task duration 877ms 924ms +5%
subgraph-transition-enter: script duration 16ms 17ms +9%
subgraph-transition-enter: TBT 116ms 138ms +19%
subgraph-transition-enter: heap used 91.8 MB 95.0 MB +3%
subgraph-transition-enter: DOM nodes 13673 13673 +0%
subgraph-transition-enter: event listeners 2375 2375 +0%
viewport-pan-sweep: avg frame time 17ms 17ms +0%
viewport-pan-sweep: p95 frame time 17ms 17ms +1%
viewport-pan-sweep: layout duration 0ms 0ms +0%
viewport-pan-sweep: style recalc duration 37ms 43ms +14%
viewport-pan-sweep: layout count 0 0 +0%
viewport-pan-sweep: style recalc count 248 250 +1%
viewport-pan-sweep: task duration 4112ms 4118ms +0%
viewport-pan-sweep: script duration 1027ms 988ms -4%
viewport-pan-sweep: TBT 0ms 0ms +0%
viewport-pan-sweep: heap used 79.9 MB 79.0 MB -1%
viewport-pan-sweep: DOM nodes -268 -249 -7%
viewport-pan-sweep: event listeners -131 -131 +0%
vue-large-graph-idle: avg frame time 18ms 18ms +0%
vue-large-graph-idle: p95 frame time 17ms 17ms +0%
vue-large-graph-idle: layout duration 0ms 0ms +0%
vue-large-graph-idle: style recalc duration 0ms 0ms +0%
vue-large-graph-idle: layout count 0 0 +0%
vue-large-graph-idle: style recalc count 0 0 +0%
vue-large-graph-idle: task duration 17315ms 17594ms +2%
vue-large-graph-idle: script duration 123ms 124ms +1%
vue-large-graph-idle: TBT 0ms 0ms +0%
vue-large-graph-idle: heap used 167.5 MB 170.0 MB +2%
vue-large-graph-idle: DOM nodes -8312 -8312 +0%
vue-large-graph-idle: event listeners -16387 -16388 +0%
vue-large-graph-pan: avg frame time 18ms 18ms +3%
vue-large-graph-pan: p95 frame time 17ms 17ms +0%
vue-large-graph-pan: layout duration 0ms 0ms +0%
vue-large-graph-pan: style recalc duration 20ms 22ms +10%
vue-large-graph-pan: layout count 0 0 +0%
vue-large-graph-pan: style recalc count 178 173 -3%
vue-large-graph-pan: task duration 21312ms 21862ms +3%
vue-large-graph-pan: script duration 408ms 468ms +15%
vue-large-graph-pan: TBT 107ms 132ms +23%
vue-large-graph-pan: heap used 181.9 MB 175.4 MB -4%
vue-large-graph-pan: DOM nodes -8312 -8314 +0%
vue-large-graph-pan: event listeners -16381 -16382 +0%
workflow-execution: avg frame time 17ms 17ms -0% z=0.1
workflow-execution: p95 frame time 17ms 17ms -1%
workflow-execution: layout duration 1ms 1ms -35% z=-4.9
workflow-execution: style recalc duration 21ms 17ms -16% z=-3.1
workflow-execution: layout count 3 3 -17% z=-4.5
workflow-execution: style recalc count 17 11 -38% z=-3.6
workflow-execution: task duration 106ms 88ms -17% z=-3.3
workflow-execution: script duration 7ms 6ms -15% z=-7.8
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: heap used 62.5 MB 61.5 MB -1%
workflow-execution: DOM nodes 143 126 -12% z=-5.0
workflow-execution: event listeners 99 62 -37% ⚠️ z=2.3
Historical variance (last 15 runs)
Metric μ σ CV
canvas-idle: avg frame time 17ms 0ms 0.0%
canvas-idle: layout duration 0ms 0ms 0.0%
canvas-idle: style recalc duration 11ms 1ms 8.2%
canvas-idle: layout count 0 0 0.0%
canvas-idle: style recalc count 11 1 5.0%
canvas-idle: task duration 395ms 31ms 7.9%
canvas-idle: script duration 25ms 2ms 8.8%
canvas-idle: TBT 0ms 0ms 0.0%
canvas-idle: DOM nodes 23 1 5.6%
canvas-idle: event listeners 12 5 40.9%
canvas-mouse-sweep: avg frame time 17ms 0ms 0.0%
canvas-mouse-sweep: layout duration 4ms 0ms 5.4%
canvas-mouse-sweep: style recalc duration 43ms 3ms 7.4%
canvas-mouse-sweep: layout count 12 0 0.0%
canvas-mouse-sweep: style recalc count 79 2 3.0%
canvas-mouse-sweep: task duration 865ms 58ms 6.7%
canvas-mouse-sweep: script duration 136ms 6ms 4.8%
canvas-mouse-sweep: TBT 0ms 0ms 0.0%
canvas-mouse-sweep: DOM nodes 62 3 4.2%
canvas-mouse-sweep: event listeners 8 4 49.4%
canvas-zoom-sweep: avg frame time 17ms 0ms 0.0%
canvas-zoom-sweep: layout duration 1ms 0ms 7.0%
canvas-zoom-sweep: style recalc duration 19ms 2ms 8.0%
canvas-zoom-sweep: layout count 6 0 0.0%
canvas-zoom-sweep: style recalc count 31 0 1.5%
canvas-zoom-sweep: task duration 327ms 23ms 7.1%
canvas-zoom-sweep: script duration 27ms 3ms 11.1%
canvas-zoom-sweep: TBT 0ms 0ms 0.0%
canvas-zoom-sweep: DOM nodes 79 1 1.0%
canvas-zoom-sweep: event listeners 24 5 21.8%
dom-widget-clipping: avg frame time 17ms 0ms 0.0%
dom-widget-clipping: layout duration 0ms 0ms 0.0%
dom-widget-clipping: style recalc duration 10ms 1ms 8.0%
dom-widget-clipping: layout count 0 0 0.0%
dom-widget-clipping: style recalc count 13 0 3.8%
dom-widget-clipping: task duration 365ms 16ms 4.5%
dom-widget-clipping: script duration 68ms 3ms 4.8%
dom-widget-clipping: TBT 0ms 0ms 0.0%
dom-widget-clipping: DOM nodes 22 1 6.4%
dom-widget-clipping: event listeners 8 6 81.2%
large-graph-idle: avg frame time 17ms 0ms 0.0%
large-graph-idle: layout duration 0ms 0ms 0.0%
large-graph-idle: style recalc duration 12ms 1ms 8.6%
large-graph-idle: layout count 0 0 0.0%
large-graph-idle: style recalc count 12 0 2.7%
large-graph-idle: task duration 542ms 54ms 10.0%
large-graph-idle: script duration 102ms 11ms 10.3%
large-graph-idle: TBT 0ms 0ms 0.0%
large-graph-idle: DOM nodes 25 1 3.7%
large-graph-idle: event listeners 26 6 23.2%
large-graph-pan: avg frame time 17ms 0ms 0.0%
large-graph-pan: layout duration 0ms 0ms 0.0%
large-graph-pan: style recalc duration 17ms 1ms 4.6%
large-graph-pan: layout count 0 0 0.0%
large-graph-pan: style recalc count 70 1 0.9%
large-graph-pan: task duration 1082ms 43ms 4.0%
large-graph-pan: script duration 408ms 20ms 4.8%
large-graph-pan: TBT 0ms 0ms 0.0%
large-graph-pan: DOM nodes 19 2 8.7%
large-graph-pan: event listeners 5 1 16.8%
minimap-idle: avg frame time 17ms 0ms 0.0%
minimap-idle: layout duration 0ms 0ms 0.0%
minimap-idle: style recalc duration 10ms 1ms 8.6%
minimap-idle: layout count 0 0 0.0%
minimap-idle: style recalc count 10 1 7.1%
minimap-idle: task duration 527ms 47ms 9.0%
minimap-idle: script duration 98ms 10ms 10.1%
minimap-idle: TBT 0ms 0ms 0.0%
minimap-idle: DOM nodes 19 1 7.1%
minimap-idle: event listeners 5 1 14.4%
subgraph-dom-widget-clipping: avg frame time 17ms 0ms 0.0%
subgraph-dom-widget-clipping: layout duration 0ms 0ms 0.0%
subgraph-dom-widget-clipping: style recalc duration 13ms 1ms 7.4%
subgraph-dom-widget-clipping: layout count 0 0 0.0%
subgraph-dom-widget-clipping: style recalc count 48 1 1.2%
subgraph-dom-widget-clipping: task duration 378ms 18ms 4.9%
subgraph-dom-widget-clipping: script duration 128ms 6ms 4.9%
subgraph-dom-widget-clipping: TBT 0ms 0ms 0.0%
subgraph-dom-widget-clipping: DOM nodes 22 1 5.0%
subgraph-dom-widget-clipping: event listeners 16 6 36.0%
subgraph-idle: avg frame time 17ms 0ms 0.0%
subgraph-idle: layout duration 0ms 0ms 0.0%
subgraph-idle: style recalc duration 10ms 1ms 7.5%
subgraph-idle: layout count 0 0 0.0%
subgraph-idle: style recalc count 11 1 6.0%
subgraph-idle: task duration 370ms 31ms 8.5%
subgraph-idle: script duration 20ms 3ms 13.2%
subgraph-idle: TBT 0ms 0ms 0.0%
subgraph-idle: DOM nodes 22 1 6.9%
subgraph-idle: event listeners 10 7 64.5%
subgraph-mouse-sweep: avg frame time 17ms 0ms 0.0%
subgraph-mouse-sweep: layout duration 5ms 0ms 6.8%
subgraph-mouse-sweep: style recalc duration 42ms 3ms 7.8%
subgraph-mouse-sweep: layout count 16 0 0.0%
subgraph-mouse-sweep: style recalc count 80 2 2.4%
subgraph-mouse-sweep: task duration 766ms 69ms 9.0%
subgraph-mouse-sweep: script duration 101ms 7ms 6.5%
subgraph-mouse-sweep: TBT 0ms 0ms 0.0%
subgraph-mouse-sweep: DOM nodes 67 2 3.3%
subgraph-mouse-sweep: event listeners 8 4 52.6%
workflow-execution: avg frame time 17ms 0ms 0.0%
workflow-execution: layout duration 2ms 0ms 9.4%
workflow-execution: style recalc duration 24ms 2ms 9.1%
workflow-execution: layout count 5 1 11.0%
workflow-execution: style recalc count 18 2 11.5%
workflow-execution: task duration 123ms 11ms 8.8%
workflow-execution: script duration 29ms 3ms 10.2%
workflow-execution: TBT 0ms 0ms 0.0%
workflow-execution: DOM nodes 161 7 4.4%
workflow-execution: event listeners 52 4 8.4%
Trend (last 15 commits on main)
Metric Trend Dir Latest
canvas-idle: avg frame time ▆▃▆▁▆▃▆█▆▆▄▃▃▄▃ ➡️ 17ms
canvas-idle: p95 frame time ➡️ NaNms
canvas-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: style recalc duration ▇▇▆▆▃█▄▃▄▃▇▄▁▆▇ ➡️ 11ms
canvas-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
canvas-idle: style recalc count █▃▅▂▅▆▃▁▂▁▂▅▆▅▆ ➡️ 12
canvas-idle: task duration ▃▃▃▆▂▃▃▅▆▂█▃▁▃▃ ➡️ 391ms
canvas-idle: script duration ▄▃▅▇▂▅▃▆▇▅█▄▁▅▆ ➡️ 27ms
canvas-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: heap used ➡️ NaN MB
canvas-idle: DOM nodes █▇▆▅▃▇▃▁▂▂▅▆▆▆▇ ➡️ 24
canvas-idle: event listeners ▅█▅▄▁▅▁▁▁▄▅▅▁▅▄ 📉 11
canvas-mouse-sweep: avg frame time ▆█▆▃▁▃▁▆▆▁▃▆▆▃▃ ➡️ 17ms
canvas-mouse-sweep: p95 frame time ➡️ NaNms
canvas-mouse-sweep: layout duration ▁▃▂▄▁▂▁▃▆▂█▇▆▄▃ ➡️ 4ms
canvas-mouse-sweep: style recalc duration ▄▄▂▄▁▂▃▃▅▄█▆▂▄▄ ➡️ 43ms
canvas-mouse-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 12
canvas-mouse-sweep: style recalc count █▅▄▃▂▂▁▄▄▅▆▅▂▇▄ ➡️ 79
canvas-mouse-sweep: task duration █▆▄▂▂▃▂▄▄▅█▆▁▆▄ ➡️ 868ms
canvas-mouse-sweep: script duration ▄▅▄▆▄▆▆▆▅▅█▆▁▅▆ ➡️ 139ms
canvas-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-mouse-sweep: heap used ➡️ NaN MB
canvas-mouse-sweep: DOM nodes █▅▃▃▁▂▂▃▂▄▆▅▃▅▅ ➡️ 64
canvas-mouse-sweep: event listeners █▁▁▁▁▁▇▁▁▁██▇▁█ 📈 13
canvas-zoom-sweep: avg frame time ▅▅█▄▅▁▁▁▅▁▁▅▄▅▁ ➡️ 17ms
canvas-zoom-sweep: p95 frame time ➡️ NaNms
canvas-zoom-sweep: layout duration ▆▅▅▄▁▁█▅▃▅▇▆▁▂▆ ➡️ 1ms
canvas-zoom-sweep: style recalc duration ▆▅▄▆▅▃█▆▇▅▇▄▁▃▅ ➡️ 20ms
canvas-zoom-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 6
canvas-zoom-sweep: style recalc count ▁▁▃▄▆▃▆█▄▄▆▁▆▁▆ ➡️ 32
canvas-zoom-sweep: task duration ▄▂▁▇▂▂▄▅▆▃█▄▁▁▅ ➡️ 338ms
canvas-zoom-sweep: script duration ▃▃▂▇▂▂▅▇▆▅█▄▁▂▆ ➡️ 30ms
canvas-zoom-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-zoom-sweep: heap used ➡️ NaN MB
canvas-zoom-sweep: DOM nodes ▄▃▁▅█▁▃▆▄▅▅▃▃▄▃ ➡️ 79
canvas-zoom-sweep: event listeners ▁▁▂▅█▂▁▅▁▅▅▄▁▅▁ ➡️ 19
dom-widget-clipping: avg frame time ▂▄▅▅▂▄█▇▅▇▇▅▅▁▇ ➡️ 17ms
dom-widget-clipping: p95 frame time ➡️ NaNms
dom-widget-clipping: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: style recalc duration ▆▆▂▆▄▃██▄▁▆▇▆▃▅ ➡️ 10ms
dom-widget-clipping: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
dom-widget-clipping: style recalc count ▇█▅█▅▄█▇▇▁▇▄▇▂▅ ➡️ 13
dom-widget-clipping: task duration ▃▃▁▅▄▃▅▆▅▂▇█▁▅▅ ➡️ 371ms
dom-widget-clipping: script duration ▅▄▄▆▆▅▇▇▆▃█▇▁▇▇ ➡️ 71ms
dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: heap used ➡️ NaN MB
dom-widget-clipping: DOM nodes ▇▇▄▇▅▄█▇▅▁▅▄▇▃▄ ➡️ 21
dom-widget-clipping: event listeners ▅▅▅▅▁▅██▁▁▁▁█▁▁ 📉 2
large-graph-idle: avg frame time ▅▅▅▅▅▂▁▂▄▅▄▂▂▅█ ➡️ 17ms
large-graph-idle: p95 frame time ➡️ NaNms
large-graph-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: style recalc duration ▅▅▅▆▄▅▃▄▅▅▆█▁▄▆ ➡️ 13ms
large-graph-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-idle: style recalc count █▆█▃▃▁▃▆▃▆▆▃▆██ ➡️ 12
large-graph-idle: task duration ▂▃▂▆▂▃▃▇▅▃██▁▂▅ ➡️ 569ms
large-graph-idle: script duration ▄▅▄▆▄▅▅▇▆▅█▆▁▃▆ ➡️ 110ms
large-graph-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: heap used ➡️ NaN MB
large-graph-idle: DOM nodes ▆█▅▂▅▃▁▂▃▅▅▆▂▆▅ ➡️ 25
large-graph-idle: event listeners ███▇██▄▁▄▇▇█▂█▇ ➡️ 29
large-graph-pan: avg frame time ▆▃▃▆█▃▁█▆▆▆▆█▁▆ ➡️ 17ms
large-graph-pan: p95 frame time ➡️ NaNms
large-graph-pan: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: style recalc duration ▃▂▄▄▁▅▂▂▁▄▄█▃▁▂ ➡️ 17ms
large-graph-pan: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-pan: style recalc count ▆▃█▂▃▂▂▂▁▇▅▃█▆▃ ➡️ 69
large-graph-pan: task duration ▄▃▄▆▄▄▄▆▄▄█▆▁▂▅ ➡️ 1100ms
large-graph-pan: script duration ▅▄▅▆▆▅▄▆▄▅█▄▁▄▅ ➡️ 413ms
large-graph-pan: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: heap used ➡️ NaN MB
large-graph-pan: DOM nodes ▅▃▆▂▄▁▃▁▁▅▁▂█▅▂ ➡️ 18
large-graph-pan: event listeners █▆█▁▁▆▁▁▃▆▁▃██▃ ➡️ 5
minimap-idle: avg frame time ▃▆▆▃█▁█▆▆▃▃▆█▆█ ➡️ 17ms
minimap-idle: p95 frame time ➡️ NaNms
minimap-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: style recalc duration ▄█▁█▅▅█▅▅▃▅▁▁▄▆ ➡️ 10ms
minimap-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
minimap-idle: style recalc count ▃▅▂▄█▃▆▁▂▅▂▁▅▆▃ ➡️ 9
minimap-idle: task duration ▃▄▁▅▁▃▄▅▇▃█▅▁▁▅ ➡️ 547ms
minimap-idle: script duration ▄▆▃▇▃▅▆▆▇▅█▅▁▃▆ ➡️ 106ms
minimap-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: heap used ➡️ NaN MB
minimap-idle: DOM nodes ▃▅▂▄█▃▆▁▂▅▂▁▅▆▃ ➡️ 19
minimap-idle: event listeners ▃▃▆▁▁▁▃▁▁▆▁▃█▆▁ ➡️ 4
subgraph-dom-widget-clipping: avg frame time ▅▄▄▄▄▄█▄▄▄▃▁▆▃▃ ➡️ 17ms
subgraph-dom-widget-clipping: p95 frame time ➡️ NaNms
subgraph-dom-widget-clipping: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: style recalc duration ▂▄▃▅▅▃▂▅▇▃▄█▁▄▆ ➡️ 14ms
subgraph-dom-widget-clipping: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-dom-widget-clipping: style recalc count ▇█▆▃▆▃▁▆█▇▃▆▇█▅ ➡️ 48
subgraph-dom-widget-clipping: task duration ▂▃▃▆▅▅▂▅█▂▆█▁▂▇ ➡️ 398ms
subgraph-dom-widget-clipping: script duration ▃▃▃▄▅▅▂▄█▂▅▇▁▂▅ ➡️ 131ms
subgraph-dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: heap used ➡️ NaN MB
subgraph-dom-widget-clipping: DOM nodes ▅▇▅▂▅▂▁▅▅▅▁▇▅█▄ ➡️ 22
subgraph-dom-widget-clipping: event listeners ▅▅▅▂▅▁▅██▁▁█▅█▅ 📈 16
subgraph-idle: avg frame time ▆▆█▁▆▃▆▆▆▃▆▁▃▆█ ➡️ 17ms
subgraph-idle: p95 frame time ➡️ NaNms
subgraph-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: style recalc duration ▁▇▃▆▂▄▂▃▃▆▆▄▃▇█ ➡️ 12ms
subgraph-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-idle: style recalc count ▃▆▃▃▂▅▁▂▁▆▃▃██▇ ➡️ 12
subgraph-idle: task duration ▁▃▁▇▁▁▃▆▅▂█▅▁▁▄ ➡️ 378ms
subgraph-idle: script duration ▁▃▂▇▁▂▃▇▆▂█▅▂▁▅ ➡️ 22ms
subgraph-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: heap used ➡️ NaN MB
subgraph-idle: DOM nodes ▃▅▃▂▁▄▁▂▁▅▃▂▇█▇ ➡️ 24
subgraph-idle: event listeners ▁▅▁▁▁▁▁▁▁▅▄▁███ 📈 21
subgraph-mouse-sweep: avg frame time ▅▄▁▃▃▄▆▄▆▃▃█▁▃▃ ➡️ 17ms
subgraph-mouse-sweep: p95 frame time ➡️ NaNms
subgraph-mouse-sweep: layout duration ▁▄▄▄▃▃▅▅▅▂█▇▂▃▆ ➡️ 5ms
subgraph-mouse-sweep: style recalc duration ▃▂▄▅▂▃▄▅█▃█▆▁▂▅ ➡️ 43ms
subgraph-mouse-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 16
subgraph-mouse-sweep: style recalc count ▅▂▅▅▁▄▃▅█▅▆▄▂▄▅ ➡️ 81
subgraph-mouse-sweep: task duration ▃▂▄▅▂▄▄▅▇▄█▆▁▃▅ ➡️ 785ms
subgraph-mouse-sweep: script duration ▄▅▄▇▅▅▆▇▆▅██▁▄▆ ➡️ 105ms
subgraph-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-mouse-sweep: heap used ➡️ NaN MB
subgraph-mouse-sweep: DOM nodes ▅▁▄▅▁▄▃▃█▅▅▄▂▅▃ ➡️ 66
subgraph-mouse-sweep: event listeners ▇▁▂▇▁▂▂▂█▇▂▂▇▇▂ 📈 5
workflow-execution: avg frame time ▆▆▆▄▆▆▃▄▁▄█▆▅▄▆ ➡️ 17ms
workflow-execution: p95 frame time ➡️ NaNms
workflow-execution: layout duration ▁▆▁▃▂▄▃▂▃▃▅█▄▂▅ ➡️ 2ms
workflow-execution: style recalc duration ▃▇▅▇▁▅▆▇█▁██▂▄▆ ➡️ 25ms
workflow-execution: layout count ▁█▂▃▂▃▃▁▃▃▄▃▂▃▂ ➡️ 5
workflow-execution: style recalc count ▃█▅▇▁▄▅▆▅▅▅▅▄▄▂ ➡️ 15
workflow-execution: task duration ▂▅▄▅▁▄▆▆▆▁▇█▁▃▃ ➡️ 120ms
workflow-execution: script duration ▄▃▄▄▃▅▄▅▆▂▇█▁▃▄ ➡️ 29ms
workflow-execution: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
workflow-execution: heap used ➡️ NaN MB
workflow-execution: DOM nodes ▂█▃▆▁▄▃▅▃█▃▃▄▃▁ ➡️ 152
workflow-execution: event listeners ▅███▁▅███▁██▅█▅ ➡️ 49
Raw data
{
  "timestamp": "2026-08-18T21:21:27.181Z",
  "gitSha": "829a33f6b2fd9b05b5f77a12e50900fee583892a",
  "branch": "fix/fe-685-previewany-null-safety",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2138.420999999994,
      "styleRecalcs": 7,
      "styleRecalcDurationMs": 7.184999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 608.1229999999999,
      "heapDeltaBytes": -25932,
      "heapUsedBytes": 60289520,
      "domNodes": -283,
      "jsHeapTotalBytes": 4972544,
      "scriptDurationMs": 8.292000000000002,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-idle",
      "durationMs": 2068.0609999999433,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.778999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 522.028,
      "heapDeltaBytes": -2828568,
      "heapUsedBytes": 58227996,
      "domNodes": -283,
      "jsHeapTotalBytes": 5496832,
      "scriptDurationMs": 8.249999999999998,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1881.732999999997,
      "styleRecalcs": 73,
      "styleRecalcDurationMs": 39.003,
      "layouts": 12,
      "layoutDurationMs": 3.513,
      "taskDurationMs": 879.9840000000002,
      "heapDeltaBytes": 368360,
      "heapUsedBytes": 60903920,
      "domNodes": -282,
      "jsHeapTotalBytes": 4448256,
      "scriptDurationMs": 110.773,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1835.472999999979,
      "styleRecalcs": 71,
      "styleRecalcDurationMs": 34.421,
      "layouts": 12,
      "layoutDurationMs": 3.258,
      "taskDurationMs": 864.178,
      "heapDeltaBytes": 13512424,
      "heapUsedBytes": 73961880,
      "domNodes": -283,
      "jsHeapTotalBytes": 4972544,
      "scriptDurationMs": 105.54100000000001,
      "eventListeners": -183,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1730.2360000000476,
      "styleRecalcs": 31,
      "styleRecalcDurationMs": 16.605999999999998,
      "layouts": 6,
      "layoutDurationMs": 0.6880000000000001,
      "taskDurationMs": 354.105,
      "heapDeltaBytes": 2792528,
      "heapUsedBytes": 63155748,
      "domNodes": 76,
      "jsHeapTotalBytes": 4980736,
      "scriptDurationMs": 9.883999999999999,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1719.920000000002,
      "styleRecalcs": 32,
      "styleRecalcDurationMs": 17.747,
      "layouts": 6,
      "layoutDurationMs": 0.631,
      "taskDurationMs": 357.544,
      "heapDeltaBytes": 2821484,
      "heapUsedBytes": 63246296,
      "domNodes": 78,
      "jsHeapTotalBytes": 5242880,
      "scriptDurationMs": 10.109,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 563.603999999998,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 6.753999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 347.96000000000004,
      "heapDeltaBytes": 10751144,
      "heapUsedBytes": 71179712,
      "domNodes": 16,
      "jsHeapTotalBytes": 4980736,
      "scriptDurationMs": 52.470000000000006,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 579.845999999975,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 6.976,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 359.17299999999994,
      "heapDeltaBytes": 10403244,
      "heapUsedBytes": 70787032,
      "domNodes": 18,
      "jsHeapTotalBytes": 4980736,
      "scriptDurationMs": 52.242999999999995,
      "eventListeners": 2,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2065.27200000005,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 6.909000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 606.6279999999999,
      "heapDeltaBytes": 2846276,
      "heapUsedBytes": 76812860,
      "domNodes": -262,
      "jsHeapTotalBytes": -2363392,
      "scriptDurationMs": 15.592999999999996,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2039.6779999999808,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.745999999999997,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 621.4730000000001,
      "heapDeltaBytes": 2341356,
      "heapUsedBytes": 76667660,
      "domNodes": -261,
      "jsHeapTotalBytes": -2101248,
      "scriptDurationMs": 16.507,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2154.075999999975,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 15.572,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1167.97,
      "heapDeltaBytes": 10888732,
      "heapUsedBytes": 84878652,
      "domNodes": -283,
      "jsHeapTotalBytes": 4939776,
      "scriptDurationMs": 315.11400000000003,
      "eventListeners": -179,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2080.4709999999886,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 10.979,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1110.1080000000002,
      "heapDeltaBytes": 17381972,
      "heapUsedBytes": 78685024,
      "domNodes": -273,
      "jsHeapTotalBytes": 225280,
      "scriptDurationMs": 305.521,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3128.0849999999987,
      "styleRecalcs": 64,
      "styleRecalcDurationMs": 13.806,
      "layouts": 60,
      "layoutDurationMs": 7.938999999999998,
      "taskDurationMs": 1342.072,
      "heapDeltaBytes": 15494424,
      "heapUsedBytes": 77227732,
      "domNodes": -272,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 381.532,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3171.9799999999623,
      "styleRecalcs": 63,
      "styleRecalcDurationMs": 14.051000000000004,
      "layouts": 60,
      "layoutDurationMs": 8.393,
      "taskDurationMs": 1379.313,
      "heapDeltaBytes": 14744760,
      "heapUsedBytes": 77017656,
      "domNodes": -273,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 398.106,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "legacy-node-drag",
      "durationMs": 2424.443999999994,
      "styleRecalcs": 45,
      "styleRecalcDurationMs": 12.215,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1508.4520000000002,
      "heapDeltaBytes": -18928936,
      "heapUsedBytes": 63801920,
      "domNodes": -248,
      "jsHeapTotalBytes": 6619136,
      "scriptDurationMs": 481.92799999999994,
      "eventListeners": 33,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "legacy-node-drag",
      "durationMs": 2393.389999999954,
      "styleRecalcs": 46,
      "styleRecalcDurationMs": 17.326999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1480.804,
      "heapDeltaBytes": -19632804,
      "heapUsedBytes": 63001252,
      "domNodes": -255,
      "jsHeapTotalBytes": 8716288,
      "scriptDurationMs": 477.643,
      "eventListeners": 33,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2052.1100000000274,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 9.413000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 606.4100000000001,
      "heapDeltaBytes": -4229720,
      "heapUsedBytes": 76409024,
      "domNodes": -267,
      "jsHeapTotalBytes": 3473408,
      "scriptDurationMs": 15.096000000000002,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 2049.4900000001053,
      "styleRecalcs": 7,
      "styleRecalcDurationMs": 6.228999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 602.603,
      "heapDeltaBytes": -3891692,
      "heapUsedBytes": 76912412,
      "domNodes": -268,
      "jsHeapTotalBytes": 3473408,
      "scriptDurationMs": 22.362999999999996,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 577.7659999999969,
      "styleRecalcs": 46,
      "styleRecalcDurationMs": 9.605999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 382.549,
      "heapDeltaBytes": 11408232,
      "heapUsedBytes": 72107512,
      "domNodes": 18,
      "jsHeapTotalBytes": 5242880,
      "scriptDurationMs": 117.33000000000001,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 576.4279999999644,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 11.202,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 387.718,
      "heapDeltaBytes": 11878012,
      "heapUsedBytes": 72692528,
      "domNodes": 20,
      "jsHeapTotalBytes": 6029312,
      "scriptDurationMs": 121.03100000000002,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2013.2950000000278,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.000000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 490.62899999999996,
      "heapDeltaBytes": 10960776,
      "heapUsedBytes": 71817720,
      "domNodes": -280,
      "jsHeapTotalBytes": 4710400,
      "scriptDurationMs": 6.8249999999999975,
      "eventListeners": -183,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2008.3419999999705,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.690999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 503.668,
      "heapDeltaBytes": -340804,
      "heapUsedBytes": 60395356,
      "domNodes": -280,
      "jsHeapTotalBytes": 3923968,
      "scriptDurationMs": 7.845999999999999,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1727.6640000000043,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 36.263,
      "layouts": 16,
      "layoutDurationMs": 4.782,
      "taskDurationMs": 801.0649999999999,
      "heapDeltaBytes": -2777732,
      "heapUsedBytes": 57946160,
      "domNodes": -280,
      "jsHeapTotalBytes": 5234688,
      "scriptDurationMs": 86.316,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1707.763,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 35.768,
      "layouts": 16,
      "layoutDurationMs": 4.5760000000000005,
      "taskDurationMs": 801.046,
      "heapDeltaBytes": -945756,
      "heapUsedBytes": 59634236,
      "domNodes": -281,
      "jsHeapTotalBytes": 5234688,
      "scriptDurationMs": 85.32799999999999,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-transition-enter",
      "durationMs": 1401.2749999999983,
      "styleRecalcs": 18,
      "styleRecalcDurationMs": 30.908999999999992,
      "layouts": 13,
      "layoutDurationMs": 13.124000000000002,
      "taskDurationMs": 923.8679999999999,
      "heapDeltaBytes": 23749776,
      "heapUsedBytes": 99614372,
      "domNodes": 13673,
      "jsHeapTotalBytes": 16515072,
      "scriptDurationMs": 17.33300000000001,
      "eventListeners": 2375,
      "totalBlockingTimeMs": 138,
      "frameDurationMs": 16.666666666666636,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8309.55300000005,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 42.29900000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4125.395,
      "heapDeltaBytes": 9205904,
      "heapUsedBytes": 83391336,
      "domNodes": -263,
      "jsHeapTotalBytes": -827392,
      "scriptDurationMs": 993.2750000000001,
      "eventListeners": -131,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8233.022000000004,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 43.059,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4111.445,
      "heapDeltaBytes": 8299672,
      "heapUsedBytes": 82270892,
      "domNodes": -235,
      "jsHeapTotalBytes": -303104,
      "scriptDurationMs": 983.125,
      "eventListeners": -131,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 18716.62900000001,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 17702.883,
      "heapDeltaBytes": -40687340,
      "heapUsedBytes": 178754780,
      "domNodes": -8312,
      "jsHeapTotalBytes": -5443584,
      "scriptDurationMs": 131.93500000000003,
      "eventListeners": -16389,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.776666666666763,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 18526.796999999988,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 17484.322,
      "heapDeltaBytes": -42226212,
      "heapUsedBytes": 177799996,
      "domNodes": -8312,
      "jsHeapTotalBytes": -7245824,
      "scriptDurationMs": 115.775,
      "eventListeners": -16387,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.77333333333336,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 22632.810000000005,
      "styleRecalcs": 172,
      "styleRecalcDurationMs": 22.08899999999997,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 22022.874000000003,
      "heapDeltaBytes": -21631780,
      "heapUsedBytes": 183941512,
      "domNodes": -8316,
      "jsHeapTotalBytes": -14168064,
      "scriptDurationMs": 427.62199999999996,
      "eventListeners": -16379,
      "totalBlockingTimeMs": 48,
      "frameDurationMs": 18.330000000000048,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 22278.65499999996,
      "styleRecalcs": 173,
      "styleRecalcDurationMs": 21.496000000000016,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 21700.760000000002,
      "heapDeltaBytes": -22873916,
      "heapUsedBytes": 183983004,
      "domNodes": -8312,
      "jsHeapTotalBytes": -13381632,
      "scriptDurationMs": 509.21000000000004,
      "eventListeners": -16385,
      "totalBlockingTimeMs": 215,
      "frameDurationMs": 18.333333333333332,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "workflow-execution",
      "durationMs": 462.1309999999994,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 19.217,
      "layouts": 2,
      "layoutDurationMs": 0.446,
      "taskDurationMs": 103.877,
      "heapDeltaBytes": 4950660,
      "heapUsedBytes": 65494356,
      "domNodes": 124,
      "jsHeapTotalBytes": 262144,
      "scriptDurationMs": 7.866000000000001,
      "eventListeners": 99,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "workflow-execution",
      "durationMs": 113.62099999996644,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 15.691,
      "layouts": 3,
      "layoutDurationMs": 1.226,
      "taskDurationMs": 71.51299999999999,
      "heapDeltaBytes": 2891276,
      "heapUsedBytes": 63548132,
      "domNodes": 127,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 3.8909999999999987,
      "eventListeners": 25,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.700000000000728
    }
  ]
}

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

@@            Coverage Diff             @@
##             main   #14073      +/-   ##
==========================================
+ Coverage   78.99%   81.49%   +2.49%     
==========================================
  Files        2209     1882     -327     
  Lines      114923   110428    -4495     
  Branches    35275    33792    -1483     
==========================================
- Hits        90788    89994     -794     
+ Misses      23665    20056    -3609     
+ Partials      470      378      -92     
Flag Coverage Δ
unit 72.96% <100.00%> (+0.07%) ⬆️
website-unit ?

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

Files with missing lines Coverage Δ
src/extensions/core/textPreviewWidgets.ts 87.50% <100.00%> (+1.78%) ⬆️

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

@jaeone94 jaeone94 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the diff, then re-ran the new suite against the pre-fix implementation (git checkout 335adad^ -- src/extensions/core/textPreviewWidgets.ts, then vitest) to see which tests actually discriminate. Numbers below come from that run.

The production change looks right to me. Normalizing in this consumer is proportionate — output.text has exactly one reader in the tree — and the parameter widening is contravariant, so nothing breaks for callers of the window.comfyAPI.textPreviewWidgets shim. I also want to explicitly agree with the Review Focus note: shallow String() over JSON.stringify is the right call, and I am not asking for object handling that nobody has reported.

My substantive concern is the test suite. 11 of the 15 new unit tests, and 100% of the new e2e spec, pass against the code this commit replaces. The four that fail — null message, undefined message, [null, null], ['first', null, 'second'] — carry the entire regression story. Details inline, along with a question about what the numeric payload actually looked like, since that determines whether one of the claimed failure modes existed at all.

Out of scope and not this PR's job, but worth a ticket: src/scripts/api.ts:843 casts unvalidated WebSocket JSON with JSON.parse(event.data) as ApiMessageUnion, while zExecutedWsMessage declares output: zOutputs and is never parsed on that path. Every consumer downstream of that cast is exposed to the same class of payload this PR is defending against.

Comment on lines +69 to +78
function toPreviewText(text: unknown): string {
if (typeof text === 'string') return text
if (text == null) return ''
if (Array.isArray(text))
return text
.filter((part) => part != null)
.map((part) => (typeof part === 'string' ? part : String(part)))
.join('\n\n')
return String(text)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestion: two of the four branches are no-ops, and dropping them makes the contract easier to read.

Array.prototype.join already calls String() on every non-nullish element, so the .map(...) is identity once .filter(...) has run. And String(s) === s for strings, so the typeof text === 'string' fast path returns exactly what the trailing return String(text) would.

function toPreviewText(text: unknown): string {
  if (text == null) return ''
  if (Array.isArray(text)) return text.filter((part) => part != null).join('\n\n')
  return String(text)
}

I diffed both versions over 19 inputs — nullish, [], [null], ['a', null, 'b'], [30, 23.976], bare scalars, objects, nested arrays — with no divergence. The .filter is the load-bearing piece; deleting it fails two tests.

Side benefit: return String(text) becomes the path strings take, so it stops being the line Codecov flags as uncovered.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

✅ Applied in 731afa2. Dropped the typeof text === 'string' fast path and the .map() - both were no-ops as you showed. Three branches, identical behavior, and return String(text) is now the string path so the Codecov flag is gone too.

export function updateTextPreviewWidgets(
node: LGraphNode,
message: { text?: string | string[] }
message: { text?: string | string[] } | null | undefined

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestion: this is a hand-written spelling of a type that already has a name, and the nullable form has precedent for exactly this wire data — src/stores/resultItemParsing.ts:32 and src/renderer/extensions/linearMode/flattenNodeOutput.ts:7 both type it as NodeExecutionOutput | null | undefined.

import type { NodeExecutionOutput } from '@/schemas/apiSchema'

export function updateTextPreviewWidgets(
  node: LGraphNode,
  message: NodeExecutionOutput | null | undefined
) {

Both call sites already pass NodeExecutionOutput (previewAny.ts:33 via the onExecuted augmentation, previewAny.ts:39 via onNodeOutputsUpdated), so nothing at the call sites changes. I applied it locally: pnpm typecheck is clean and the 18 tests still pass.

The as unknown as casts in the test file survive that change, and I think that is worth noticing rather than working around — zOutputs.text is declared string | string[] while the premise of this PR is that it can be neither. The casts are the schema and reality disagreeing out loud.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

✅ Applied in 231dea6. Switched to NodeExecutionOutput | null | undefined - matches the pattern in resultItemParsing.ts and flattenNodeOutput.ts as you noted. The casts in the test file survive the change as expected.

Comment on lines +133 to +147
it.each([
['compact JSON', '{"name":"Comfy","emoji":"🌟"}'],
[
'pretty JSON',
'{\n "name": "Comfy",\n "arr": [\n 1,\n 2\n ]\n}'
],
['markdown-fenced JSON', '```json\n{"name":"Comfy"}\n```'],
['JSON array', '[{"a": 1}, {"b": 2}]'],
['non-ASCII text', '你好,世界。'],
['prompt with trailing space', '"A red car" is a great prompt. '],
['prompt ending in a quoted period', "ending in 'best quality.'"]
])('renders %s verbatim', (_label, text) => {
updateTextPreviewWidgets(node, { text })
expect(node.widgets[0].value).toBe(text)
})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Issue: these seven rows all exercise one branch — if (typeof text === 'string') return text. The module does no parsing, trimming or escaping, so compact JSON, pretty JSON, fenced JSON, a JSON array, non-ASCII, a trailing space and a quoted period are seven spellings of the same identity assertion, and writes a plain string message as-is at line 99 already covers it. All seven stay green against the pre-fix implementation.

They also imply a guarantee this module cannot give. The payloads listed can only be mangled on the render side — renderMarkdownToHtml in WidgetTextPreview.vue — where WidgetTextPreview.test.ts already has a working harness. Fenced JSON through markdown rendering is a real test; fenced JSON through x => x is not.

Suggest dropping the block, keeping at most one row, and spending the budget on the branch nothing currently reaches (see my question on the numeric case above).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

✅ Applied in 6a10a9d. Dropped the 7-row block and replaced it with a single test for the bare scalar { text: 23.976 } - the only shape that genuinely reaches return String(text) and the one that was actually blank before the fix.

Comment on lines +125 to +131
it('renders non-string values rather than blanking the node', () => {
updateTextPreviewWidgets(node, { text: [30.0, 23.976] } as unknown as {
text: string[]
})

expect(node.widgets[0].value).toBe('30\n\n23.976')
})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Question: what did the payload actually look like when the numeric case was reported?

[30.0, 23.976].join('\n\n') is '30\n\n23.976' on the pre-fix implementation too, so this test passes against the code the commit replaces — for arrays, the unguarded join produced the right answer. The shape that genuinely broke is a bare non-array value:

  • {"text": 23.976} — pre-fix this assigned a number to preview.value, and setValue (line 46 of the source file) coerces non-strings to '', so the node went blank. That matches the reported symptom, and return String(text) is what fixes it.
  • {"text": [23.976]} — rendered correctly before and after.

If Get Video Components sent the first shape, a test for it would be the only one in this file that reaches return String(text):

it('stringifies a bare non-string payload', () => {
  updateTextPreviewWidgets(node, { text: 23.976 } as unknown as { text: string })
  expect(node.widgets[0].value).toBe('23.976')
})

I ran that against the pre-fix implementation and it fails with expected 23.976 to be '23.976'. If it was the second shape, the non-string failure mode may be worth dropping from the commit message instead.

Either way, one caveat about what this layer can prove: beforeEach pushes a plain { name: 'preview_text', options: {}, value: '' } object, so preview.value = ... is a bare property write and the setValue coercion — the actual blanking mechanism — is never in the assertion path. The unit tests can demonstrate stringification, but not "rather than blanking the node".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good analysis. The bare non-string scalar shape ({ text: 23.976 }) is what the fix targets - added a unit test for it in 6a10a9d and an e2e step in 91c26ea that sends it unwrapped. Your point about setValue coercion not being in the assertion path is fair - the unit test proves stringification happens, but the e2e step is the one that exercises the actual blanking prevention end to end.

Comment on lines +100 to +115
for (const [label, text] of payloads) {
await test.step(label, async () => {
execution.executed('', id, { text: [text] })
await expect(preview).toHaveValue(text)
})
}

await test.step('null text does not wedge the widget', async () => {
// The shape the Cloud backend produced when it misclassified the text
// as a filename and dropped it from the payload (BE-3601).
execution.executed('', id, { text: [null] })
await expect(preview).toHaveValue('')

execution.executed('', id, { text: ['recovered'] })
await expect(preview).toHaveValue('recovered')
})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Issue: every frame this spec sends yields the same assertion result against the pre-fix implementation, so it cannot detect the bug in its own commit.

  • The six payload rows are strings inside arrays; text.join('\n\n') returned them unchanged before the fix.
  • { text: [null] }[null].join('\n\n') was already '', so nothing was ever wedged by that shape, and the recovered follow-up passed before too.
  • {}message.text ?? '' was already ''.

The failure that needed fixing is message itself being nullish, and this spec structurally cannot send it: ExecutionHelper.executed() types output as Record<string, unknown>, and app.ts:803 forwards detail.output unmodified.

Two ways to give the spec teeth, both covering ground the unit tests cannot reach:

  • widen ExecutionHelper.executed's output to unknown and drive a null-output executed frame, asserting the widget renders empty and still updates on the next frame;
  • send { text: 23.976 } unquoted and unwrapped, which is the only way to exercise the real setValue string guard end to end.

Either way the six-row loop could collapse to one representative payload — those characters are already covered at the unit layer, and six browser round-trips is a lot for one identity path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Added a dedicated step in 91c26ea that sends { text: 23.976 } as a bare scalar - this is the shape that would have been blank before the fix and is the only one the loop couldn't exercise. The 6-row loop is also trimmed to 5 representative strings. The ExecutionHelper.executed output-widening approach would be stronger for null-message coverage but that's a bigger change; happy to do it in a followup if you think it's worth it.

['markdown-fenced JSON', '```json\n{"name":"Comfy"}\n```'],
['non-ASCII text', '你好,世界。'],
['prompt with a trailing space', '"A red car" is a great prompt. '],
['numeric output from Get Video Components', '23.976']

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: the label says numeric output, but the payload is the string '23.976' wrapped in an array, which makes this row indistinguishable from the five above it. Sending 23.976 unquoted and unwrapped is what would exercise the new String() path — otherwise the label overclaims.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

✅ Fixed in 91c26ea. Moved the numeric case to a separate step that sends 23.976 as a bare unwrapped scalar rather than a string in an array.

await comfyPage.menu.topbar.newWorkflowButton.click()
await comfyPage.searchBoxV2.addNode('Preview as Text')
const node = await comfyPage.vueNodes.getFixtureByTitle('Preview as Text')
const preview = node.root.locator('textarea')

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: raw CSS element selector, and this is the second copy in the file (line 58 has the first). WidgetTextPreview.vue sets :aria-label="widget.name", and VueNodeHelpers.getWidgetByName(nodeTitle, widgetName) already resolves it by label:

const preview = comfyPage.vueNodes.getWidgetByName('Preview as Text', 'preview_text')

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

✅ Fixed in 82b60d5. Both occurrences replaced with comfyPage.vueNodes.getWidgetByName('Preview as Text', 'preview_text'). The node variable was kept in the workflow-restore test since it's still used for the visibility assertions, and dropped entirely from the payloads test where it had no other purpose.

@jaeone94 jaeone94 assigned christian-byrne and unassigned jaeone94 Jul 31, 2026
christian-byrne added a commit that referenced this pull request Aug 12, 2026
Array.prototype.join already calls String() on non-nullish elements, so
the .map() was identity after .filter(). The typeof string fast path
returned the same result as the trailing String(text), so it was a no-op
too. Four branches collapse to three with identical behavior.

Side benefit: return String(text) is now the path strings take, fixing
the Codecov uncovered-line flag.

Addresses review feedback:
#14073 (comment)
christian-byrne added a commit that referenced this pull request Aug 12, 2026
Uses the canonical type from apiSchema instead of a hand-written inline
shape. Both call sites (previewAny.ts onExecuted and onNodeOutputsUpdated)
already pass NodeExecutionOutput, so nothing at the call sites changes.
Matches the pattern in resultItemParsing.ts:32 and flattenNodeOutput.ts:7.

Addresses review feedback:
#14073 (comment)
christian-byrne added a commit that referenced this pull request Aug 12, 2026
The 7-row it.each block all exercised the typeof-string identity path,
which was already covered by 'writes a plain string message as-is'.
The bare scalar {text: 23.976} is the only input that reaches
return String(text) and the only shape that was actually broken before
the fix (setValue coerces non-strings to '', blanking the widget).

Addresses review feedback:
#14073 (comment)
christian-byrne added a commit that referenced this pull request Aug 12, 2026
The previous row sent '23.976' as a string inside an array, which was
indistinguishable from the string payloads above it. A bare number
{text: 23.976} is the shape that exercises return String(text) and the
one that was blank before the fix.

Addresses review feedback:
#14073 (comment)
christian-byrne added a commit that referenced this pull request Aug 12, 2026
WidgetTextPreview.vue sets :aria-label="widget.name", so the widget can
be located by label instead of by element type. Removes the duplicate
raw CSS selector at both occurrences and uses the established helper.

Addresses review feedback:
#14073 (comment)

@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

🤖 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 `@src/extensions/core/textPreviewWidgets.test.ts`:
- Around line 138-143: Remove the no-throw-only test case for
updateTextPreviewWidgets when the node lacks a preview widget, since it asserts
no observable behavior beyond an existing safe no-op guard.
🪄 Autofix

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: 2920c147-e89c-4d64-aaf6-0325cb28e78c

📥 Commits

Reviewing files that changed from the base of the PR and between b5d17f9 and 82b60d5.

📒 Files selected for processing (3)
  • browser_tests/tests/previewAsText.spec.ts
  • src/extensions/core/textPreviewWidgets.test.ts
  • src/extensions/core/textPreviewWidgets.ts

Comment thread src/extensions/core/textPreviewWidgets.test.ts Outdated
jaeone94
jaeone94 previously approved these changes Aug 12, 2026
@jaeone94 jaeone94 assigned christian-byrne and unassigned jaeone94 Aug 12, 2026
@jaeone94

Copy link
Copy Markdown
Collaborator

CodeRabbit's point seems valid.

christian-byrne added a commit that referenced this pull request Aug 17, 2026
Array.prototype.join already calls String() on non-nullish elements, so
the .map() was identity after .filter(). The typeof string fast path
returned the same result as the trailing String(text), so it was a no-op
too. Four branches collapse to three with identical behavior.

Side benefit: return String(text) is now the path strings take, fixing
the Codecov uncovered-line flag.

Addresses review feedback:
#14073 (comment)
christian-byrne added a commit that referenced this pull request Aug 17, 2026
Uses the canonical type from apiSchema instead of a hand-written inline
shape. Both call sites (previewAny.ts onExecuted and onNodeOutputsUpdated)
already pass NodeExecutionOutput, so nothing at the call sites changes.
Matches the pattern in resultItemParsing.ts:32 and flattenNodeOutput.ts:7.

Addresses review feedback:
#14073 (comment)
christian-byrne added a commit that referenced this pull request Aug 17, 2026
The 7-row it.each block all exercised the typeof-string identity path,
which was already covered by 'writes a plain string message as-is'.
The bare scalar {text: 23.976} is the only input that reaches
return String(text) and the only shape that was actually broken before
the fix (setValue coerces non-strings to '', blanking the widget).

Addresses review feedback:
#14073 (comment)
christian-byrne added a commit that referenced this pull request Aug 17, 2026
The previous row sent '23.976' as a string inside an array, which was
indistinguishable from the string payloads above it. A bare number
{text: 23.976} is the shape that exercises return String(text) and the
one that was blank before the fix.

Addresses review feedback:
#14073 (comment)
christian-byrne added a commit that referenced this pull request Aug 17, 2026
WidgetTextPreview.vue sets :aria-label="widget.name", so the widget can
be located by label instead of by element type. Removes the duplicate
raw CSS selector at both occurrences and uses the established helper.

Addresses review feedback:
#14073 (comment)

for (const [label, text] of payloads) {
await test.step(label, async () => {
execution.executed('', id, { text: [text] })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The repeated empty string as the first argument smells to me.

expect(node.widgets[0].value).toBe('hello')
})

it.each([

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.

Shouldn't the linter require it.for instead?

it('renders non-string entries rather than blanking the node', () => {
updateTextPreviewWidgets(node, {
text: ['first', 23.976, null, 'second']
} as unknown as { text: 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.

Shoehorn?

})

it('stringifies a bare non-string scalar payload', () => {
updateTextPreviewWidgets(node, { text: 23.976 } as unknown as {

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.

Same here.

DrJKL
DrJKL previously approved these changes Aug 17, 2026
@DrJKL DrJKL assigned christian-byrne and unassigned DrJKL Aug 17, 2026
christian-byrne and others added 10 commits August 17, 2026 21:05
updateTextPreviewWidgets read `message.text` off an unvalidated WebSocket
payload. Three failure modes, all reported against the Preview as Text node:

- `message` null/undefined threw `Cannot read properties of null (reading
  'text')`, which aborted the update and left the node showing stale or empty
  content.
- `{"text": [null]}` joined to an empty string, rendering the node blank. Cloud
  produces exactly this shape when it misclassifies a text output as a filename,
  fails to upload it, and drops it from the payload.
- `{"text": ["a", null]}` rendered a leading blank separator, and non-string
  entries (e.g. an FPS number) went through an unguarded join.

Normalize the payload before assigning: nullish becomes an empty string, null
entries are filtered out of arrays, and non-string values are stringified rather
than blanking the node.

This is the frontend half of the "Preview as Text is blank" reports. The
upstream cause of the nulled payloads is fixed separately in the cloud repo;
this change makes the node degrade to empty instead of throwing, and render
correctly as soon as real text arrives.

Tests: unit coverage for every payload shape above plus the exact strings users
reported blank (LLM JSON in four shapes, non-ASCII, trailing-space prompts,
numeric outputs), and an e2e spec driving those payloads through the WebSocket
that also asserts the widget recovers after a null payload.

- Fixes FE-685
Array.prototype.join already calls String() on non-nullish elements, so
the .map() was identity after .filter(). The typeof string fast path
returned the same result as the trailing String(text), so it was a no-op
too. Four branches collapse to three with identical behavior.

Side benefit: return String(text) is now the path strings take, fixing
the Codecov uncovered-line flag.

Addresses review feedback:
#14073 (comment)
Uses the canonical type from apiSchema instead of a hand-written inline
shape. Both call sites (previewAny.ts onExecuted and onNodeOutputsUpdated)
already pass NodeExecutionOutput, so nothing at the call sites changes.
Matches the pattern in resultItemParsing.ts:32 and flattenNodeOutput.ts:7.

Addresses review feedback:
#14073 (comment)
The 7-row it.each block all exercised the typeof-string identity path,
which was already covered by 'writes a plain string message as-is'.
The bare scalar {text: 23.976} is the only input that reaches
return String(text) and the only shape that was actually broken before
the fix (setValue coerces non-strings to '', blanking the widget).

Addresses review feedback:
#14073 (comment)
The previous row sent '23.976' as a string inside an array, which was
indistinguishable from the string payloads above it. A bare number
{text: 23.976} is the shape that exercises return String(text) and the
one that was blank before the fix.

Addresses review feedback:
#14073 (comment)
WidgetTextPreview.vue sets :aria-label="widget.name", so the widget can
be located by label instead of by element type. Removes the duplicate
raw CSS selector at both occurrences and uses the established helper.

Addresses review feedback:
#14073 (comment)
@christian-byrne
christian-byrne force-pushed the fix/fe-685-previewany-null-safety branch from ff23ae3 to 7b0ab98 Compare August 18, 2026 21:07
@christian-byrne
christian-byrne requested a review from DrJKL August 18, 2026 21:31
@christian-byrne
christian-byrne merged commit c814b8e into main Aug 23, 2026
68 of 69 checks passed
@christian-byrne
christian-byrne deleted the fix/fe-685-previewany-null-safety branch August 23, 2026 00:35
christian-byrne added a commit that referenced this pull request Aug 23, 2026
`AGENTS.md` says "Never mention Claude/AI in commits", but nothing
enforces it here. ComfyUI core already has this check; this ports it.

It matters more in this repo than in core: `main` is squash-merge only,
and squash does not strip trailers from the composed message, so a
trailer on any commit lands in `main`'s history.

This is not hypothetical —
#14073 carried
`Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>` on commit
`4afc68a7c3` through a full green CI run. It was caught by hand and
stripped.

Verified against that exact commit range, red-green:

- `947e35b..ff23ae3` (before the fix) → exit 1, reports `4afc68a7c3:
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>`
- `947e35b..7b0ab98` (after) → exit 0, "No AI agent Co-authored-by
trailers found"

Both trees are byte-identical, so the trailer is the only variable.

Two changes from core's copy to match this repo: `actions/checkout@v7`
(`.pinact.yaml` exempts checkout only at `v7`, so core's `@v4` would
fail `Validate Action SHA Pins`), and the `ci-*.yaml` filename
convention. Added `permissions: contents: read`.

The script covers 12 agent vendors by trailer email plus generic name
patterns, so it is not Claude-specific.

---------

Co-authored-by: DrJKL <DrJKL0424@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:R2 PR risk grade (advisory shadow check; grader-owned) size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants