Skip to content

[TEMP] feat: add interactive video stream widgets#13587

Draft
bigcat88 wants to merge 2 commits into
mainfrom
feat/interactive-webcam-stream
Draft

[TEMP] feat: add interactive video stream widgets#13587
bigcat88 wants to merge 2 commits into
mainfrom
feat/interactive-webcam-stream

Conversation

@bigcat88

Copy link
Copy Markdown
Contributor

Summary

Adds a first-class interactive node surface for bidirectional video streaming over ComfyUI's existing WebSocket connection.

The demo renderer shows a local webcam preview and a backend-processed result side by side. Python node definitions declare the surface through interactive_ui, so the same frontend implementation can be used by local ComfyUI and Comfy Cloud.

Protocol

  • versioned JSON control messages for open, ready, credit, stop, cancel, resume, and close
  • bounded binary type-5 frames with validated metadata
  • prompt_id routing for cloud job ownership
  • credit-based capture and WebSocket backpressure
  • reconnect negotiation and durable terminal intent
  • server capability and frame-limit negotiation

Cloud can support this without a separate frontend branch by relaying the same interaction messages behind its existing /ws endpoint.

Demo behavior

  1. Start the webcam from the node surface.
  2. Queue the interactive node.
  3. Frames are sent to the backend as JPEGs.
  4. Processed frames are displayed in the result surface.
  5. Stopping the stream completes execution and returns the backend's final VIDEO output.

Requires the corresponding draft ComfyUI backend interaction broker and demo node changes.

Validation

  • 121 targeted tests passed
  • pnpm typecheck
  • ESLint and Oxlint on touched files
  • pnpm knip --cache
  • production Vite build
  • real browser E2E: webcam capture, 26 source/result frames, stop, close, and node completion

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

🌐 Website E2E

Tip

All tests passed.

Status ✅ Passed
Report View Report

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 07/11/2026, 12:28:50 PM UTC

Links

🎭 Playwright: ✅ 1690 passed, 0 failed · 4 flaky

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

📦 Bundle: 7.83 MB gzip 🔴 +5.08 kB

Details

Summary

  • Raw size: 33 MB baseline 33 MB — 🔴 +21.8 kB
  • Gzip: 7.83 MB baseline 7.83 MB — 🔴 +5.08 kB
  • Brotli: 5.4 MB baseline 5.39 MB — 🔴 +4.07 kB
  • Bundles: 303 current • 303 baseline • 136 added / 136 removed

Category Glance
Utilities & Hooks 🔴 +15.5 kB (3.41 MB) · Data & Services 🔴 +6.34 kB (276 kB) · Graph Workspace 🔴 +1 B (1.27 MB) · Vendor & Third-Party ⚪ 0 B (15.3 MB) · Other ⚪ 0 B (11.8 MB) · Panels & Settings ⚪ 0 B (550 kB) · + 5 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-7CAyZK4Q.js (removed) 53.2 kB 🟢 -53.2 kB 🟢 -15.7 kB 🟢 -13.5 kB
assets/index-Ce6TjvKE.js (new) 53.2 kB 🔴 +53.2 kB 🔴 +15.7 kB 🔴 +13.5 kB

Status: 1 added / 1 removed

Graph Workspace — 1.27 MB (baseline 1.27 MB) • 🔴 +1 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-DlP-DoOU.js (new) 1.27 MB 🔴 +1.27 MB 🔴 +271 kB 🔴 +204 kB
assets/GraphView-Ci7inVKr.js (removed) 1.27 MB 🟢 -1.27 MB 🟢 -271 kB 🟢 -204 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-BKXZCn3k.js (removed) 25 kB 🟢 -25 kB 🟢 -6.26 kB 🟢 -5.54 kB
assets/CloudSurveyView-C1TUCUAA.js (new) 25 kB 🔴 +25 kB 🔴 +6.26 kB 🔴 +5.53 kB
assets/CloudLoginView-D0fqYJc5.js (removed) 11.4 kB 🟢 -11.4 kB 🟢 -3.07 kB 🟢 -2.68 kB
assets/CloudLoginView-Di2R3Wcd.js (new) 11.4 kB 🔴 +11.4 kB 🔴 +3.06 kB 🔴 +2.69 kB
assets/CloudSignupView-BA_Bcg1U.js (new) 9.79 kB 🔴 +9.79 kB 🔴 +2.74 kB 🔴 +2.4 kB
assets/CloudSignupView-WXUyu3eu.js (removed) 9.79 kB 🟢 -9.79 kB 🟢 -2.74 kB 🟢 -2.4 kB
assets/CloudLayoutView-C6bcJldr.js (new) 9.59 kB 🔴 +9.59 kB 🔴 +2.37 kB 🔴 +2.05 kB
assets/CloudLayoutView-CUub80Aw.js (removed) 9.59 kB 🟢 -9.59 kB 🟢 -2.37 kB 🟢 -2.06 kB
assets/UserCheckView-BFDQRAx1.js (new) 8.8 kB 🔴 +8.8 kB 🔴 +2.22 kB 🔴 +1.93 kB
assets/UserCheckView-PX1a5dYC.js (removed) 8.8 kB 🟢 -8.8 kB 🟢 -2.22 kB 🟢 -1.93 kB
assets/CloudSubscriptionRedirectView-Bjk2iUxz.js (removed) 6.98 kB 🟢 -6.98 kB 🟢 -2.58 kB 🟢 -2.25 kB
assets/CloudSubscriptionRedirectView-vnziOMvg.js (new) 6.98 kB 🔴 +6.98 kB 🔴 +2.58 kB 🔴 +2.26 kB
assets/WidgetTextPreview-CrVJcmB9.js (removed) 6.13 kB 🟢 -6.13 kB 🟢 -2.15 kB 🟢 -1.91 kB
assets/WidgetTextPreview-CU5fNdCH.js (new) 6.13 kB 🔴 +6.13 kB 🔴 +2.15 kB 🔴 +1.91 kB
assets/UserSelectView-CF_qc0LK.js (removed) 6 kB 🟢 -6 kB 🟢 -2.15 kB 🟢 -1.89 kB
assets/UserSelectView-DtlVQiB2.js (new) 6 kB 🔴 +6 kB 🔴 +2.15 kB 🔴 +1.89 kB
assets/CloudForgotPasswordView-BcVw8-Ou.js (new) 5.15 kB 🔴 +5.15 kB 🔴 +1.76 kB 🔴 +1.54 kB
assets/CloudForgotPasswordView-BpypGI7b.js (removed) 5.15 kB 🟢 -5.15 kB 🟢 -1.76 kB 🟢 -1.54 kB
assets/CloudAuthTimeoutView-CFFgpEj4.js (removed) 4.49 kB 🟢 -4.49 kB 🟢 -1.58 kB 🟢 -1.37 kB
assets/CloudAuthTimeoutView-Tqs2szBP.js (new) 4.49 kB 🔴 +4.49 kB 🔴 +1.58 kB 🔴 +1.37 kB
assets/WidgetTextPreview-BOV4HOjv.js (new) 131 B 🔴 +131 B 🔴 +100 B 🔴 +91 B
assets/WidgetTextPreview-CaL7o6Ho.js (removed) 131 B 🟢 -131 B 🟢 -100 B 🟢 -89 B

Status: 11 added / 11 removed / 3 unchanged

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-BYcuiL-F.js (new) 49.4 kB 🔴 +49.4 kB 🔴 +9.97 kB 🔴 +8.82 kB
assets/KeybindingPanel-Dp1PHcc2.js (removed) 49.4 kB 🟢 -49.4 kB 🟢 -9.97 kB 🟢 -8.83 kB
assets/SecretsPanel-B4r82f3-.js (new) 27.3 kB 🔴 +27.3 kB 🔴 +6.63 kB 🔴 +5.81 kB
assets/SecretsPanel-fNiFD-wH.js (removed) 27.3 kB 🟢 -27.3 kB 🟢 -6.63 kB 🟢 -5.81 kB
assets/CreditsPanel-CEtfYyLC.js (removed) 15.9 kB 🟢 -15.9 kB 🟢 -4.66 kB 🟢 -4.1 kB
assets/CreditsPanel-MX7FLYVt.js (new) 15.9 kB 🔴 +15.9 kB 🔴 +4.66 kB 🔴 +4.09 kB
assets/AboutPanel-B2VAeZZF.js (removed) 12 kB 🟢 -12 kB 🟢 -3.29 kB 🟢 -2.94 kB
assets/AboutPanel-DcLN4clE.js (new) 12 kB 🔴 +12 kB 🔴 +3.29 kB 🔴 +2.94 kB
assets/SubscriptionPanel-B80YxUdD.js (new) 11.5 kB 🔴 +11.5 kB 🔴 +3.65 kB 🔴 +3.21 kB
assets/SubscriptionPanel-BdGWab_b.js (removed) 11.5 kB 🟢 -11.5 kB 🟢 -3.65 kB 🟢 -3.2 kB
assets/ExtensionPanel-DAN4__CM.js (new) 9.03 kB 🔴 +9.03 kB 🔴 +2.5 kB 🔴 +2.2 kB
assets/ExtensionPanel-DhhkVQGx.js (removed) 9.03 kB 🟢 -9.03 kB 🟢 -2.5 kB 🟢 -2.2 kB
assets/ServerConfigPanel-BZpxYn2l.js (new) 6.15 kB 🔴 +6.15 kB 🔴 +1.97 kB 🔴 +1.75 kB
assets/ServerConfigPanel-DoMRc3dz.js (removed) 6.15 kB 🟢 -6.15 kB 🟢 -1.97 kB 🟢 -1.75 kB
assets/UserPanel-DcC61_rN.js (new) 5.78 kB 🔴 +5.78 kB 🔴 +1.82 kB 🔴 +1.58 kB
assets/UserPanel-De5rTw3y.js (removed) 5.78 kB 🟢 -5.78 kB 🟢 -1.82 kB 🟢 -1.58 kB
assets/refreshRemoteConfig-BxJom5oV.js (removed) 2.88 kB 🟢 -2.88 kB 🟢 -1.26 kB 🟢 -1.11 kB
assets/refreshRemoteConfig-DxnTxR4K.js (new) 2.88 kB 🔴 +2.88 kB 🔴 +1.26 kB 🔴 +1.11 kB
assets/cloudRemoteConfig-JBSfX1Kr.js (removed) 990 B 🟢 -990 B 🟢 -542 B 🟢 -463 B
assets/cloudRemoteConfig-ljqZhcvv.js (new) 990 B 🔴 +990 B 🔴 +543 B 🔴 +460 B
assets/refreshRemoteConfig-C7-puOcn.js (removed) 110 B 🟢 -110 B 🟢 -89 B 🟢 -93 B
assets/refreshRemoteConfig-DGQ4DEKp.js (new) 110 B 🔴 +110 B 🔴 +89 B 🔴 +91 B

Status: 11 added / 11 removed / 16 unchanged

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SignUpForm-COcC3i-W.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +4.19 kB 🔴 +3.65 kB
assets/SignUpForm-Dhx-_on7.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -4.19 kB 🟢 -3.66 kB
assets/auth-BZ4BOa-Y.js (new) 3.69 kB 🔴 +3.69 kB 🔴 +1.3 kB 🔴 +1.13 kB
assets/auth-CGQv_7iU.js (removed) 3.69 kB 🟢 -3.69 kB 🟢 -1.3 kB 🟢 -1.13 kB
assets/usePostAuthRedirect-CYC1S5Ii.js (removed) 3.33 kB 🟢 -3.33 kB 🟢 -1.27 kB 🟢 -1.11 kB
assets/usePostAuthRedirect-Lu_RRa-L.js (new) 3.33 kB 🔴 +3.33 kB 🔴 +1.27 kB 🔴 +1.11 kB
assets/UpdatePasswordContent-BgiaPRLQ.js (removed) 1.92 kB 🟢 -1.92 kB 🟢 -878 B 🟢 -767 B
assets/UpdatePasswordContent-DgpYMHOR.js (new) 1.92 kB 🔴 +1.92 kB 🔴 +878 B 🔴 +767 B
assets/authStore-B5vigAo6.js (removed) 130 B 🟢 -130 B 🟢 -109 B 🟢 -121 B
assets/authStore-DI1kjA3v.js (new) 130 B 🔴 +130 B 🔴 +109 B 🔴 +111 B
assets/workspaceAuthStore-B6q-UDIN.js (new) 110 B 🔴 +110 B 🔴 +104 B 🔴 +114 B
assets/workspaceAuthStore-mfn-Y6uh.js (removed) 110 B 🟢 -110 B 🟢 -104 B 🟢 -112 B
assets/auth-Dpp_dVK7.js (new) 105 B 🔴 +105 B 🔴 +96 B 🔴 +86 B
assets/auth-DqdltSLi.js (removed) 105 B 🟢 -105 B 🟢 -96 B 🟢 -80 B

Status: 7 added / 7 removed / 3 unchanged

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

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyHubPublishDialog-C4wco8Fk.js (removed) 90.5 kB 🟢 -90.5 kB 🟢 -19.3 kB 🟢 -16.5 kB
assets/ComfyHubPublishDialog-CWReLGWm.js (new) 90.5 kB 🔴 +90.5 kB 🔴 +19.3 kB 🔴 +16.5 kB
assets/useShareDialog-BFb38PyO.js (removed) 23.7 kB 🟢 -23.7 kB 🟢 -5.58 kB 🟢 -4.97 kB
assets/useShareDialog-BWks1ip4.js (new) 23.7 kB 🔴 +23.7 kB 🔴 +5.58 kB 🔴 +4.96 kB
assets/ComfyHubPublishDialog-DxRNi-Kc.js (new) 143 B 🔴 +143 B 🔴 +105 B 🔴 +92 B
assets/ComfyHubPublishDialog-K3Z3vWLO.js (removed) 143 B 🟢 -143 B 🟢 -105 B 🟢 -87 B
assets/useSubscriptionDialog-BlnEm1C_.js (new) 110 B 🔴 +110 B 🔴 +102 B 🔴 +88 B
assets/useSubscriptionDialog-CqseAV9Y.js (removed) 110 B 🟢 -110 B 🟢 -102 B 🟢 -94 B

Status: 4 added / 4 removed / 1 unchanged

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-BpGUxCz6.js (new) 13.6 kB 🔴 +13.6 kB 🔴 +3.82 kB 🔴 +3.41 kB
assets/ComfyQueueButton-BrJQEHSD.js (removed) 13.6 kB 🟢 -13.6 kB 🟢 -3.82 kB 🟢 -3.41 kB
assets/useTerminalTabs-Crdu6hl3.js (removed) 12.1 kB 🟢 -12.1 kB 🟢 -3.83 kB 🟢 -3.39 kB
assets/useTerminalTabs-D0fuqPkD.js (new) 12.1 kB 🔴 +12.1 kB 🔴 +3.83 kB 🔴 +3.38 kB
assets/SubscribeButton--H8KPg3s.js (new) 2.38 kB 🔴 +2.38 kB 🔴 +1.05 kB 🔴 +916 B
assets/SubscribeButton-DY6cDM9i.js (removed) 2.38 kB 🟢 -2.38 kB 🟢 -1.05 kB 🟢 -918 B
assets/cloudFeedbackTopbarButton-Cy0GcA-D.js (removed) 829 B 🟢 -829 B 🟢 -496 B 🟢 -420 B
assets/cloudFeedbackTopbarButton-DudLbMON.js (new) 829 B 🔴 +829 B 🔴 +494 B 🔴 +419 B
assets/ComfyQueueButton-B65diZXR.js (removed) 128 B 🟢 -128 B 🟢 -99 B 🟢 -92 B
assets/ComfyQueueButton-CC0rNZon.js (new) 128 B 🔴 +128 B 🔴 +99 B 🔴 +90 B

Status: 5 added / 5 removed / 8 unchanged

Data & Services — 276 kB (baseline 269 kB) • 🔴 +6.34 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/load3dService-C3Jv6gE1.js (new) 127 kB 🔴 +127 kB 🔴 +27.8 kB 🔴 +23.6 kB
assets/load3dService-D2PYdoBl.js (removed) 127 kB 🟢 -127 kB 🟢 -27.8 kB 🟢 -23.5 kB
assets/api-lxCYAC1b.js (new) 97.9 kB 🔴 +97.9 kB 🔴 +26.9 kB 🔴 +23 kB
assets/api-CHjFWakN.js (removed) 91.5 kB 🟢 -91.5 kB 🟢 -25.3 kB 🟢 -21.6 kB
assets/workflowShareService-gSR27gHO.js (removed) 17 kB 🟢 -17 kB 🟢 -5.01 kB 🟢 -4.44 kB
assets/workflowShareService-KqYYFRHr.js (new) 17 kB 🔴 +17 kB 🔴 +5.01 kB 🔴 +4.43 kB
assets/releaseStore-BWrhHWdF.js (new) 8.29 kB 🔴 +8.29 kB 🔴 +2.33 kB 🔴 +2.05 kB
assets/releaseStore-D2nLlywn.js (removed) 8.29 kB 🟢 -8.29 kB 🟢 -2.34 kB 🟢 -2.04 kB
assets/keybindingService-CQyjRqQR.js (removed) 7.46 kB 🟢 -7.46 kB 🟢 -1.92 kB 🟢 -1.65 kB
assets/keybindingService-D2M55ayo.js (new) 7.46 kB 🔴 +7.46 kB 🔴 +1.92 kB 🔴 +1.64 kB
assets/extensionStore-BipvMgjr.js (new) 5.29 kB 🔴 +5.29 kB 🔴 +1.86 kB 🔴 +1.57 kB
assets/extensionStore-BlzTk96U.js (removed) 5.29 kB 🟢 -5.29 kB 🟢 -1.86 kB 🟢 -1.57 kB
assets/userStore-CkRdrU_q.js (removed) 2.42 kB 🟢 -2.42 kB 🟢 -933 B 🟢 -823 B
assets/userStore-D81MiIXW.js (new) 2.42 kB 🔴 +2.42 kB 🔴 +933 B 🔴 +822 B
assets/audioService-CpbBEWvH.js (new) 1.76 kB 🔴 +1.76 kB 🔴 +860 B 🔴 +756 B
assets/audioService-mp_LMsBu.js (removed) 1.76 kB 🟢 -1.76 kB 🟢 -860 B 🟢 -748 B
assets/dialogService-B-017qdm.js (removed) 100 B 🟢 -100 B 🟢 -99 B 🟢 -97 B
assets/dialogService-BiJTOfVD.js (new) 100 B 🔴 +100 B 🔴 +99 B 🔴 +99 B
assets/settingStore-BUoHKiYu.js (new) 98 B 🔴 +98 B 🔴 +98 B 🔴 +91 B
assets/settingStore-C4-VrSw7.js (removed) 98 B 🟢 -98 B 🟢 -98 B 🟢 -97 B
assets/assetsStore-DWt_NbLg.js (new) 96 B 🔴 +96 B 🔴 +97 B 🔴 +88 B
assets/assetsStore-fU_D5YvE.js (removed) 96 B 🟢 -96 B 🟢 -97 B 🟢 -99 B
assets/releaseStore-CJvnHT-q.js (removed) 95 B 🟢 -95 B 🟢 -86 B 🟢 -85 B
assets/releaseStore-Dhpj_poB.js (new) 95 B 🔴 +95 B 🔴 +86 B 🔴 +85 B
assets/api-BFuxLavk.js (removed) 62 B 🟢 -62 B 🟢 -74 B 🟢 -66 B
assets/api-BqmIPlqt.js (new) 62 B 🔴 +62 B 🔴 +74 B 🔴 +66 B

Status: 13 added / 13 removed / 3 unchanged

Utilities & Hooks — 3.41 MB (baseline 3.4 MB) • 🔴 +15.5 kB

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/promotionUtils-NnTr_rqA.js (new) 3.05 MB 🔴 +3.05 MB 🔴 +705 kB 🔴 +532 kB
assets/promotionUtils-CE8E3Ipw.js (removed) 3.03 MB 🟢 -3.03 MB 🟢 -701 kB 🟢 -529 kB
assets/useConflictDetection-DxkF7LuZ.js (removed) 234 kB 🟢 -234 kB 🟢 -52.3 kB 🟢 -42.5 kB
assets/useConflictDetection-pbl1RuyJ.js (new) 234 kB 🔴 +234 kB 🔴 +52.3 kB 🔴 +42.5 kB
assets/useLoad3d-CaaS342v.js (removed) 25.7 kB 🟢 -25.7 kB 🟢 -5.82 kB 🟢 -5.15 kB
assets/useLoad3d-t7H4HBo7.js (new) 25.7 kB 🔴 +25.7 kB 🔴 +5.82 kB 🔴 +5.16 kB
assets/useLoad3dViewer-C4JEyWHG.js (removed) 21.2 kB 🟢 -21.2 kB 🟢 -4.98 kB 🟢 -4.35 kB
assets/useLoad3dViewer-CSJ8oINm.js (new) 21.2 kB 🔴 +21.2 kB 🔴 +4.98 kB 🔴 +4.35 kB
assets/useFeatureFlags-BxtbeNji.js (new) 6.51 kB 🔴 +6.51 kB 🔴 +2.02 kB 🔴 +1.7 kB
assets/useFeatureFlags-DqwO1TjS.js (removed) 6.51 kB 🟢 -6.51 kB 🟢 -2.02 kB 🟢 -1.7 kB
assets/subscriptionCheckoutUtil-B3PVsFdu.js (removed) 3.85 kB 🟢 -3.85 kB 🟢 -1.54 kB 🟢 -1.35 kB
assets/subscriptionCheckoutUtil-BHx5AFMV.js (new) 3.85 kB 🔴 +3.85 kB 🔴 +1.54 kB 🔴 +1.36 kB
assets/useSessionCookie-D_M9BlSG.js (new) 3.61 kB 🔴 +3.61 kB 🔴 +1.21 kB 🔴 +1.04 kB
assets/useSessionCookie-UpUIKP3O.js (removed) 3.61 kB 🟢 -3.61 kB 🟢 -1.21 kB 🟢 -1.04 kB
assets/useDowngradeToPersonal-BdnZsBth.js (removed) 3 kB 🟢 -3 kB 🟢 -1.18 kB 🟢 -1.04 kB
assets/useDowngradeToPersonal-jj2gxwuf.js (new) 3 kB 🔴 +3 kB 🔴 +1.18 kB 🔴 +1.02 kB
assets/assetPreviewUtil-DyP6Ai9v.js (new) 2.41 kB 🔴 +2.41 kB 🔴 +1.01 kB 🔴 +875 B
assets/assetPreviewUtil-ilYOTgOq.js (removed) 2.41 kB 🟢 -2.41 kB 🟢 -1.01 kB 🟢 -878 B
assets/useUpstreamValue-BAK1oyA0.js (new) 2.04 kB 🔴 +2.04 kB 🔴 +794 B 🔴 +704 B
assets/useUpstreamValue-CM4AEena.js (removed) 2.04 kB 🟢 -2.04 kB 🟢 -793 B 🟢 -704 B
assets/useWorkspaceTierLabel-B0zDJsYL.js (removed) 1.99 kB 🟢 -1.99 kB 🟢 -854 B 🟢 -738 B
assets/useWorkspaceTierLabel-D0Ho0Ho7.js (new) 1.99 kB 🔴 +1.99 kB 🔴 +852 B 🔴 +734 B
assets/useLoad3d-06gow9Pb.js (new) 311 B 🔴 +311 B 🔴 +165 B 🔴 +147 B
assets/useLoad3d-DT09ZnZO.js (removed) 311 B 🟢 -311 B 🟢 -162 B 🟢 -147 B
assets/useSessionCookie-5D5kPmoe.js (new) 101 B 🔴 +101 B 🔴 +86 B 🔴 +81 B
assets/useSessionCookie-DjxGE87X.js (removed) 101 B 🟢 -101 B 🟢 -86 B 🟢 -86 B
assets/useFeatureFlags-BHuQSi44.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +80 B
assets/useFeatureFlags-DBcvtorl.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -81 B
assets/useLoad3dViewer-Bxa6LW_t.js (new) 98 B 🔴 +98 B 🔴 +85 B 🔴 +86 B
assets/useLoad3dViewer-DXm5ShEk.js (removed) 98 B 🟢 -98 B 🟢 -85 B 🟢 -85 B
assets/useCurrentUser-VaPzrhQF.js (new) 96 B 🔴 +96 B 🔴 +97 B 🔴 +84 B
assets/useCurrentUser-vuIwWN02.js (removed) 96 B 🟢 -96 B 🟢 -97 B 🟢 -91 B

Status: 16 added / 16 removed / 19 unchanged

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

External libraries and shared vendor chunks

Status: 16 unchanged

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

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-p1a5qBET.js (removed) 121 kB 🟢 -121 kB 🟢 -31.1 kB 🟢 -26.3 kB
assets/core-qjAwK067.js (new) 121 kB 🔴 +121 kB 🔴 +31.1 kB 🔴 +26.3 kB
assets/WidgetSelect-CIMLxTvD.js (new) 89.3 kB 🔴 +89.3 kB 🔴 +20.1 kB 🔴 +17.2 kB
assets/WidgetSelect-COw9PSmg.js (removed) 89.3 kB 🟢 -89.3 kB 🟢 -20.1 kB 🟢 -17.2 kB
assets/Load3DControls-b_67AaS-.js (new) 46.8 kB 🔴 +46.8 kB 🔴 +7.56 kB 🔴 +6.61 kB
assets/Load3DControls-CY38CC94.js (removed) 46.8 kB 🟢 -46.8 kB 🟢 -7.57 kB 🟢 -6.61 kB
assets/SubscriptionTransitionPreviewWorkspace-BuldTvNQ.js (removed) 46.8 kB 🟢 -46.8 kB 🟢 -9.65 kB 🟢 -8.55 kB
assets/SubscriptionTransitionPreviewWorkspace-CWtBaDwA.js (new) 46.8 kB 🔴 +46.8 kB 🔴 +9.65 kB 🔴 +8.53 kB
assets/SubscriptionRequiredDialogContentUnified-Bctqr9ge.js (removed) 41.2 kB 🟢 -41.2 kB 🟢 -9.16 kB 🟢 -7.97 kB
assets/SubscriptionRequiredDialogContentUnified-DE1vArCd.js (new) 41.2 kB 🔴 +41.2 kB 🔴 +9.15 kB 🔴 +7.97 kB
assets/WorkspacePanelContent-B918Xc6o.js (new) 34.5 kB 🔴 +34.5 kB 🔴 +7.5 kB 🔴 +6.62 kB
assets/WorkspacePanelContent-UfrJ-hog.js (removed) 34.5 kB 🟢 -34.5 kB 🟢 -7.5 kB 🟢 -6.61 kB
assets/WidgetBoundingBoxes-C3STyzWN.js (new) 33.8 kB 🔴 +33.8 kB 🔴 +9.2 kB 🔴 +8.15 kB
assets/WidgetBoundingBoxes-CJN0CMPb.js (removed) 33.8 kB 🟢 -33.8 kB 🟢 -9.19 kB 🟢 -8.16 kB
assets/WidgetPainter-ClcQaZrD.js (removed) 32.7 kB 🟢 -32.7 kB 🟢 -7.9 kB 🟢 -7.01 kB
assets/WidgetPainter-DbzJh6if.js (new) 32.7 kB 🔴 +32.7 kB 🔴 +7.9 kB 🔴 +7.01 kB
assets/Load3dViewerContent-CB0mH2Px.js (removed) 30.9 kB 🟢 -30.9 kB 🟢 -6.3 kB 🟢 -5.46 kB
assets/Load3dViewerContent-Vm950zCd.js (new) 30.9 kB 🔴 +30.9 kB 🔴 +6.3 kB 🔴 +5.48 kB
assets/SubscriptionRequiredDialogContent-B9Nc25Xq.js (new) 27.4 kB 🔴 +27.4 kB 🔴 +6.75 kB 🔴 +5.97 kB
assets/SubscriptionRequiredDialogContent-sXDBWkwH.js (removed) 27.4 kB 🟢 -27.4 kB 🟢 -6.75 kB 🟢 -5.96 kB
assets/SubscriptionPanelContentWorkspace-BNuJ3gAr.js (removed) 25 kB 🟢 -25 kB 🟢 -5.84 kB 🟢 -5.13 kB
assets/SubscriptionPanelContentWorkspace-Bxx6FhHT.js (new) 25 kB 🔴 +25 kB 🔴 +5.84 kB 🔴 +5.13 kB
assets/SubscriptionRequiredDialogContentWorkspace-aqDCAjuz.js (removed) 24.6 kB 🟢 -24.6 kB 🟢 -5.66 kB 🟢 -4.97 kB
assets/SubscriptionRequiredDialogContentWorkspace-C4jTUT1Y.js (new) 24.6 kB 🔴 +24.6 kB 🔴 +5.66 kB 🔴 +4.96 kB
assets/WidgetImageCrop-DXwftOSH.js (new) 23.3 kB 🔴 +23.3 kB 🔴 +5.75 kB 🔴 +5.05 kB
assets/WidgetImageCrop-GWu1IXQz.js (removed) 23.3 kB 🟢 -23.3 kB 🟢 -5.75 kB 🟢 -5.04 kB
assets/load3d-CyK6UWh1.js (new) 22.3 kB 🔴 +22.3 kB 🔴 +5.45 kB 🔴 +4.71 kB
assets/load3d-D0SoEUhi.js (removed) 22.3 kB 🟢 -22.3 kB 🟢 -5.44 kB 🟢 -4.71 kB
assets/CurrentUserPopoverWorkspace-Bmct5BcZ.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +4.74 kB 🔴 +4.23 kB
assets/CurrentUserPopoverWorkspace-BqnjG8iJ.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -4.75 kB 🟢 -4.24 kB
assets/SignInContent-CM0mlUq3.js (new) 20.1 kB 🔴 +20.1 kB 🔴 +5.07 kB 🔴 +4.43 kB
assets/SignInContent-oqxETkYI.js (removed) 20.1 kB 🟢 -20.1 kB 🟢 -5.07 kB 🟢 -4.43 kB
assets/WidgetInputNumber-8oS4pn3f.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.82 kB 🔴 +4.29 kB
assets/WidgetInputNumber-DELcUSd4.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.82 kB 🟢 -4.29 kB
assets/Load3D-C0K17RSN.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.52 kB 🟢 -3.94 kB
assets/Load3D-CF302R1y.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.52 kB 🔴 +3.94 kB
assets/CreditsTile-BVxMKZH0.js (new) 17.1 kB 🔴 +17.1 kB 🔴 +4.54 kB 🔴 +4.01 kB
assets/CreditsTile-C89P5o4i.js (removed) 17.1 kB 🟢 -17.1 kB 🟢 -4.54 kB 🟢 -4.01 kB
assets/WidgetRecordAudio-CFZ3N2aq.js (removed) 16.6 kB 🟢 -16.6 kB 🟢 -4.63 kB 🟢 -4.14 kB
assets/WidgetRecordAudio-o2OzxAyJ.js (new) 16.6 kB 🔴 +16.6 kB 🔴 +4.63 kB 🔴 +4.14 kB
assets/WidgetRange-CYkPe980.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.16 kB 🔴 +3.72 kB
assets/WidgetRange-njnD4Ini.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.16 kB 🟢 -3.71 kB
assets/WaveAudioPlayer-D0SY-BLa.js (removed) 12.8 kB 🟢 -12.8 kB 🟢 -3.48 kB 🟢 -3.06 kB
assets/WaveAudioPlayer-DFWW9tGf.js (new) 12.8 kB 🔴 +12.8 kB 🔴 +3.48 kB 🔴 +3.06 kB
assets/WidgetCurve-C9PxjkWW.js (removed) 11.3 kB 🟢 -11.3 kB 🟢 -3.5 kB 🟢 -3.16 kB
assets/WidgetCurve-ffw3-6i9.js (new) 11.3 kB 🔴 +11.3 kB 🔴 +3.51 kB 🔴 +3.16 kB
assets/TeamWorkspacesDialogContent-5PUIMiOQ.js (removed) 10.3 kB 🟢 -10.3 kB 🟢 -3 kB 🟢 -2.68 kB
assets/TeamWorkspacesDialogContent-BfBMWR-B.js (new) 10.3 kB 🔴 +10.3 kB 🔴 +3 kB 🔴 +2.66 kB
assets/Load3DConfiguration-DWD2KNec.js (removed) 9.02 kB 🟢 -9.02 kB 🟢 -2.66 kB 🟢 -2.35 kB
assets/Load3DConfiguration-jv1fpfdr.js (new) 9.02 kB 🔴 +9.02 kB 🔴 +2.66 kB 🔴 +2.35 kB
assets/onboardingCloudRoutes-CJhH62dr.js (new) 8.5 kB 🔴 +8.5 kB 🔴 +2.66 kB 🔴 +2.29 kB
assets/onboardingCloudRoutes-Dw-dYGbT.js (removed) 8.5 kB 🟢 -8.5 kB 🟢 -2.66 kB 🟢 -2.3 kB
assets/nodeTemplates-B4BcJl-W.js (removed) 8.33 kB 🟢 -8.33 kB 🟢 -2.88 kB 🟢 -2.54 kB
assets/nodeTemplates-DtxxEcFX.js (new) 8.33 kB 🔴 +8.33 kB 🔴 +2.88 kB 🔴 +2.54 kB
assets/NightlySurveyController-BExI1PDW.js (new) 7.95 kB 🔴 +7.95 kB 🔴 +2.7 kB 🔴 +2.39 kB
assets/NightlySurveyController-D2Nrm7TR.js (removed) 7.95 kB 🟢 -7.95 kB 🟢 -2.7 kB 🟢 -2.39 kB
assets/InviteMemberDialogContent-Burn2Cor.js (new) 6.76 kB 🔴 +6.76 kB 🔴 +2.22 kB 🔴 +1.96 kB
assets/InviteMemberDialogContent-CBPABwxd.js (removed) 6.76 kB 🟢 -6.76 kB 🟢 -2.22 kB 🟢 -1.96 kB
assets/WidgetWithControl-DU-YLqHf.js (new) 6.38 kB 🔴 +6.38 kB 🔴 +2.58 kB 🔴 +2.3 kB
assets/WidgetWithControl-oI4fqVsf.js (removed) 6.38 kB 🟢 -6.38 kB 🟢 -2.58 kB 🟢 -2.3 kB
assets/tierBenefits-BgTOL1cD.js (removed) 6.02 kB 🟢 -6.02 kB 🟢 -1.93 kB 🟢 -1.68 kB
assets/tierBenefits-CpJZkPzU.js (new) 6.02 kB 🔴 +6.02 kB 🔴 +1.93 kB 🔴 +1.67 kB
assets/load3dPreviewExtensions-BZIn7Dwo.js (removed) 5.95 kB 🟢 -5.95 kB 🟢 -1.85 kB 🟢 -1.63 kB
assets/load3dPreviewExtensions-Dg9m3H6f.js (new) 5.95 kB 🔴 +5.95 kB 🔴 +1.85 kB 🔴 +1.63 kB
assets/CancelSubscriptionDialogContent-BLDeaBc1.js (removed) 5.76 kB 🟢 -5.76 kB 🟢 -1.97 kB 🟢 -1.73 kB
assets/CancelSubscriptionDialogContent-DSQyV34V.js (new) 5.76 kB 🔴 +5.76 kB 🔴 +1.97 kB 🔴 +1.73 kB
assets/FreeTierDialogContent-D42JZ055.js (removed) 5.25 kB 🟢 -5.25 kB 🟢 -1.76 kB 🟢 -1.55 kB
assets/FreeTierDialogContent-DnEU8Bcp.js (new) 5.25 kB 🔴 +5.25 kB 🔴 +1.76 kB 🔴 +1.55 kB
assets/CreateWorkspaceDialogContent-DoZc1Kdh.js (removed) 5.19 kB 🟢 -5.19 kB 🟢 -1.83 kB 🟢 -1.58 kB
assets/CreateWorkspaceDialogContent-nkV2VHHS.js (new) 5.19 kB 🔴 +5.19 kB 🔴 +1.83 kB 🔴 +1.58 kB
assets/missingModelDownload-BABIi-LN.js (new) 5.07 kB 🔴 +5.07 kB 🔴 +1.98 kB 🔴 +1.72 kB
assets/missingModelDownload-BTkjr7jK.js (removed) 5.07 kB 🟢 -5.07 kB 🟢 -1.98 kB 🟢 -1.72 kB
assets/ChangeMemberRoleDialogContent-CmTR3fFf.js (removed) 5.04 kB 🟢 -5.04 kB 🟢 -1.67 kB 🟢 -1.46 kB
assets/ChangeMemberRoleDialogContent-xP-K9brN.js (new) 5.04 kB 🔴 +5.04 kB 🔴 +1.67 kB 🔴 +1.46 kB
assets/EditWorkspaceDialogContent-CRto9cF_.js (new) 5 kB 🔴 +5 kB 🔴 +1.79 kB 🔴 +1.56 kB
assets/EditWorkspaceDialogContent-FbU8IKd8.js (removed) 5 kB 🟢 -5 kB 🟢 -1.79 kB 🟢 -1.56 kB
assets/WidgetTextarea-BxwtMppo.js (new) 4.86 kB 🔴 +4.86 kB 🔴 +1.89 kB 🔴 +1.66 kB
assets/WidgetTextarea-DLevBpf8.js (removed) 4.86 kB 🟢 -4.86 kB 🟢 -1.89 kB 🟢 -1.66 kB
assets/saveMesh-CNrcXB7c.js (removed) 4.81 kB 🟢 -4.81 kB 🟢 -1.55 kB 🟢 -1.38 kB
assets/saveMesh-DZig70iY.js (new) 4.81 kB 🔴 +4.81 kB 🔴 +1.55 kB 🔴 +1.37 kB
assets/Preview3d-CHb5d_zi.js (new) 4.59 kB 🔴 +4.59 kB 🔴 +1.43 kB 🔴 +1.24 kB
assets/Preview3d-Dncd-FFq.js (removed) 4.59 kB 🟢 -4.59 kB 🟢 -1.43 kB 🟢 -1.23 kB
assets/ValueControlPopover-3xsZB3_l.js (removed) 4.55 kB 🟢 -4.55 kB 🟢 -1.59 kB 🟢 -1.42 kB
assets/ValueControlPopover-BPU0ligE.js (new) 4.55 kB 🔴 +4.55 kB 🔴 +1.59 kB 🔴 +1.42 kB
assets/DeleteWorkspaceDialogContent-BbH7jpMJ.js (removed) 3.91 kB 🟢 -3.91 kB 🟢 -1.47 kB 🟢 -1.27 kB
assets/DeleteWorkspaceDialogContent-Rl9y8cmo.js (new) 3.91 kB 🔴 +3.91 kB 🔴 +1.47 kB 🔴 +1.27 kB
assets/LeaveWorkspaceDialogContent-Dvof-DvC.js (new) 3.73 kB 🔴 +3.73 kB 🔴 +1.42 kB 🔴 +1.22 kB
assets/LeaveWorkspaceDialogContent-tOYpWRvO.js (removed) 3.73 kB 🟢 -3.73 kB 🟢 -1.42 kB 🟢 -1.22 kB
assets/RemoveMemberDialogContent-BjNEFPWN.js (new) 3.71 kB 🔴 +3.71 kB 🔴 +1.37 kB 🔴 +1.19 kB
assets/RemoveMemberDialogContent-BQfeKabF.js (removed) 3.71 kB 🟢 -3.71 kB 🟢 -1.37 kB 🟢 -1.19 kB
assets/RevokeInviteDialogContent-C6875Q9G.js (removed) 3.63 kB 🟢 -3.63 kB 🟢 -1.38 kB 🟢 -1.21 kB
assets/RevokeInviteDialogContent-xahUuoC3.js (new) 3.63 kB 🔴 +3.63 kB 🔴 +1.38 kB 🔴 +1.21 kB
assets/InviteMemberUpsellDialogContent-A08I87j9.js (new) 3.52 kB 🔴 +3.52 kB 🔴 +1.27 kB 🔴 +1.1 kB
assets/InviteMemberUpsellDialogContent-BZXCVpxA.js (removed) 3.52 kB 🟢 -3.52 kB 🟢 -1.27 kB 🟢 -1.1 kB
assets/workspaceCheckoutTelemetry-C-aVhbfZ.js (removed) 3.47 kB 🟢 -3.47 kB 🟢 -1.56 kB 🟢 -1.36 kB
assets/workspaceCheckoutTelemetry-D1FC_IaN.js (new) 3.47 kB 🔴 +3.47 kB 🔴 +1.56 kB 🔴 +1.36 kB
assets/Media3DTop-Br7eACzP.js (removed) 3.26 kB 🟢 -3.26 kB 🟢 -1.3 kB 🟢 -1.13 kB
assets/Media3DTop-CW1U5039.js (new) 3.26 kB 🔴 +3.26 kB 🔴 +1.3 kB 🔴 +1.13 kB
assets/GlobalToast-BMBGufS7.js (new) 3.05 kB 🔴 +3.05 kB 🔴 +1.26 kB 🔴 +1.09 kB
assets/GlobalToast-qwiVFn_R.js (removed) 3.05 kB 🟢 -3.05 kB 🟢 -1.26 kB 🟢 -1.08 kB
assets/load3dAdvanced-Dqb4qZzW.js (new) 2.87 kB 🔴 +2.87 kB 🔴 +1.13 kB 🔴 +979 B
assets/load3dAdvanced-Qlcq76Ro.js (removed) 2.87 kB 🟢 -2.87 kB 🟢 -1.13 kB 🟢 -981 B
assets/SubscribeToRun-BAf9dm4R.js (removed) 2.56 kB 🟢 -2.56 kB 🟢 -1.11 kB 🟢 -976 B
assets/SubscribeToRun-HJbHLQth.js (new) 2.56 kB 🔴 +2.56 kB 🔴 +1.11 kB 🔴 +975 B
assets/graphHasMissingNodes-CfX31sa4.js (removed) 1.93 kB 🟢 -1.93 kB 🟢 -907 B 🟢 -789 B
assets/graphHasMissingNodes-DTN9C7Wi.js (new) 1.93 kB 🔴 +1.93 kB 🔴 +907 B 🔴 +812 B
assets/MediaAudioTop-CdDzvktm.js (removed) 1.67 kB 🟢 -1.67 kB 🟢 -836 B 🟢 -697 B
assets/MediaAudioTop-jC1XHD8U.js (new) 1.67 kB 🔴 +1.67 kB 🔴 +838 B 🔴 +703 B
assets/CloudRunButtonWrapper-35R-m5II.js (removed) 1.13 kB 🟢 -1.13 kB 🟢 -550 B 🟢 -489 B
assets/CloudRunButtonWrapper-C24le3Yp.js (new) 1.13 kB 🔴 +1.13 kB 🔴 +551 B 🔴 +518 B
assets/cloudSessionCookie-BDluZyr8.js (new) 991 B 🔴 +991 B 🔴 +466 B 🔴 +396 B
assets/cloudSessionCookie-BiqP-3K3.js (removed) 991 B 🟢 -991 B 🟢 -465 B 🟢 -399 B
assets/cloudBadges-BBJ2XqW7.js (new) 973 B 🔴 +973 B 🔴 +552 B 🔴 +472 B
assets/cloudBadges-C3LE1Z-n.js (removed) 973 B 🟢 -973 B 🟢 -550 B 🟢 -500 B
assets/Load3DAdvanced-Bci1Pqpy.js (removed) 813 B 🟢 -813 B 🟢 -456 B 🟢 -413 B
assets/Load3DAdvanced-ul7c1CwW.js (new) 813 B 🔴 +813 B 🔴 +455 B 🔴 +410 B
assets/nightlyBadges-B-0uFymi.js (new) 464 B 🔴 +464 B 🔴 +303 B 🔴 +254 B
assets/nightlyBadges-BCgtn8bY.js (removed) 464 B 🟢 -464 B 🟢 -304 B 🟢 -294 B
assets/missingModelDownload-o1OyQRXH.js (new) 228 B 🔴 +228 B 🔴 +150 B 🔴 +128 B
assets/missingModelDownload-R6uN_JIu.js (removed) 228 B 🟢 -228 B 🟢 -149 B 🟢 -129 B
assets/SubscriptionPanelContentWorkspace-BHey0Bm1.js (new) 179 B 🔴 +179 B 🔴 +117 B 🔴 +91 B
assets/SubscriptionPanelContentWorkspace-D5wNLITK.js (removed) 179 B 🟢 -179 B 🟢 -117 B 🟢 -90 B
assets/Load3dViewerContent-DW9z07lm.js (removed) 137 B 🟢 -137 B 🟢 -103 B 🟢 -97 B
assets/Load3dViewerContent-YtjqxXb5.js (new) 137 B 🔴 +137 B 🔴 +103 B 🔴 +91 B
assets/Load3DAdvanced-DYzgNAt9.js (new) 122 B 🔴 +122 B 🔴 +97 B 🔴 +88 B
assets/Load3DAdvanced-Ie0CallX.js (removed) 122 B 🟢 -122 B 🟢 -97 B 🟢 -87 B
assets/WidgetLegacy-B_SQc__X.js (new) 119 B 🔴 +119 B 🔴 +108 B 🔴 +94 B
assets/WidgetLegacy-CmSQQdGD.js (removed) 119 B 🟢 -119 B 🟢 -108 B 🟢 -101 B
assets/workflowDraftStoreV2-B8WZKoGl.js (new) 113 B 🔴 +113 B 🔴 +105 B 🔴 +107 B
assets/workflowDraftStoreV2-cuilypt8.js (removed) 113 B 🟢 -113 B 🟢 -105 B 🟢 -110 B
assets/Load3D-B8aNqbQ3.js (new) 98 B 🔴 +98 B 🔴 +89 B 🔴 +81 B
assets/Load3D-BGWxnYlM.js (removed) 98 B 🟢 -98 B 🟢 -89 B 🟢 -82 B
assets/changeTracker-DsrvGW-_.js (removed) 93 B 🟢 -93 B 🟢 -95 B 🟢 -88 B
assets/changeTracker-DULgP2c0.js (new) 93 B 🔴 +93 B 🔴 +95 B 🔴 +83 B

Status: 67 added / 67 removed / 98 unchanged

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.5 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 49.9 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 57.0 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 57.2 MB heap
large-graph-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 59.6 MB heap
large-graph-pan: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 78.1 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.7 MB heap
minimap-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 59.3 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 60.3 MB heap
subgraph-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 45.5 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 50.8 MB heap
subgraph-transition-enter: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 167ms TBT · 72.7 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 72.3 MB heap
vue-large-graph-idle: · 58.1 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 154.1 MB heap
vue-large-graph-pan: · 57.1 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 28ms TBT · 165.5 MB heap
workflow-execution: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 49.3 MB heap

⚠️ 5 regressions detected

Show regressions
Metric Baseline PR (median) Δ Sig
canvas-zoom-sweep: task duration 282ms 376ms +33% ⚠️ z=2.1
large-graph-pan: style recalc duration 18ms 19ms +8% ⚠️ z=2.5
large-graph-pan: task duration 826ms 1205ms +46% ⚠️ z=2.9
subgraph-mouse-sweep: layout duration 4ms 6ms +82% ⚠️ z=5.2
workflow-execution: task duration 156ms 152ms -2% ⚠️ z=2.7
All metrics
Metric Baseline PR (median) Δ Sig
canvas-idle: avg frame time 17ms 17ms -0% z=-0.1
canvas-idle: p95 frame time 17ms 17ms +0%
canvas-idle: layout duration 0ms 0ms +0%
canvas-idle: style recalc duration 8ms 8ms +8% z=-2.7
canvas-idle: layout count 0 0 +0%
canvas-idle: style recalc count 10 9 -15% z=-4.7
canvas-idle: task duration 340ms 440ms +30% z=1.5
canvas-idle: script duration 13ms 17ms +34% z=-3.6
canvas-idle: TBT 0ms 0ms +0%
canvas-idle: heap used 59.7 MB 53.5 MB -10%
canvas-idle: DOM nodes -276 -287 +4% z=-242.7
canvas-idle: event listeners -197 -199 +1% z=-44.5
canvas-mouse-sweep: avg frame time 17ms 17ms -0% z=-0.9
canvas-mouse-sweep: p95 frame time 17ms 17ms +0%
canvas-mouse-sweep: layout duration 3ms 4ms +24% z=1.1
canvas-mouse-sweep: style recalc duration 31ms 44ms +39% z=0.4
canvas-mouse-sweep: layout count 12 12 +0%
canvas-mouse-sweep: style recalc count 72 74 +3% z=-2.0
canvas-mouse-sweep: task duration 643ms 839ms +31% z=-0.4
canvas-mouse-sweep: script duration 94ms 126ms +34% z=-1.5
canvas-mouse-sweep: TBT 0ms 0ms +0%
canvas-mouse-sweep: heap used 49.8 MB 49.9 MB +0%
canvas-mouse-sweep: DOM nodes -245 -257 +5% z=-123.5
canvas-mouse-sweep: event listeners -197 -199 +1% z=-49.8
canvas-zoom-sweep: avg frame time 17ms 17ms -0% z=-0.3
canvas-zoom-sweep: p95 frame time 17ms 17ms +0%
canvas-zoom-sweep: layout duration 1ms 1ms +1% z=0.5
canvas-zoom-sweep: style recalc duration 15ms 19ms +25% z=-0.2
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 282ms 376ms +33% ⚠️ z=2.1
canvas-zoom-sweep: script duration 17ms 23ms +31% z=-1.5
canvas-zoom-sweep: TBT 0ms 0ms +0%
canvas-zoom-sweep: heap used 58.4 MB 57.0 MB -2%
canvas-zoom-sweep: DOM nodes 77 -233 -403% z=-394.5
canvas-zoom-sweep: event listeners 19 -185 -1074% z=-40.3
dom-widget-clipping: avg frame time 17ms 17ms -0% z=-0.2
dom-widget-clipping: p95 frame time 17ms 17ms -1%
dom-widget-clipping: layout duration 0ms 0ms +0%
dom-widget-clipping: style recalc duration 7ms 9ms +27% z=-1.5
dom-widget-clipping: layout count 0 0 +0%
dom-widget-clipping: style recalc count 12 11 -13% z=-5.2
dom-widget-clipping: task duration 304ms 378ms +25% z=0.8
dom-widget-clipping: script duration 45ms 60ms +36% z=-2.3
dom-widget-clipping: TBT 0ms 0ms +0%
dom-widget-clipping: heap used 54.1 MB 57.2 MB +6%
dom-widget-clipping: DOM nodes -288 -140 -52% z=-113.9
dom-widget-clipping: event listeners -201 -102 -50% variance too high
large-graph-idle: avg frame time 17ms 17ms -0% z=-1.0
large-graph-idle: p95 frame time 17ms 17ms +0%
large-graph-idle: layout duration 0ms 0ms +0%
large-graph-idle: style recalc duration 6ms 9ms +45% z=-2.9
large-graph-idle: layout count 0 0 +0%
large-graph-idle: style recalc count 10 9 -10% z=-8.3
large-graph-idle: task duration 426ms 523ms +23% z=-0.4
large-graph-idle: script duration 69ms 95ms +36% z=-0.7
large-graph-idle: TBT 0ms 0ms +0%
large-graph-idle: heap used 57.1 MB 59.6 MB +4%
large-graph-idle: DOM nodes -261 -126 -52% z=-166.2
large-graph-idle: event listeners 4 -97 -2513% z=-20.1
large-graph-pan: avg frame time 17ms 17ms +0% z=0.3
large-graph-pan: p95 frame time 17ms 17ms -1%
large-graph-pan: layout duration 0ms 0ms +0%
large-graph-pan: style recalc duration 18ms 19ms +8% ⚠️ z=2.5
large-graph-pan: layout count 0 0 +0%
large-graph-pan: style recalc count 69 69 -1% z=-1.7
large-graph-pan: task duration 826ms 1205ms +46% ⚠️ z=2.9
large-graph-pan: script duration 306ms 434ms +42% z=1.3
large-graph-pan: TBT 0ms 0ms +0%
large-graph-pan: heap used 79.2 MB 78.1 MB -1%
large-graph-pan: DOM nodes -247 -269 +9% z=-174.6
large-graph-pan: event listeners -126 -163 +29% z=-202.2
large-graph-zoom: avg frame time 17ms 17ms -0%
large-graph-zoom: p95 frame time 17ms 17ms +0%
large-graph-zoom: layout duration 7ms 8ms +13%
large-graph-zoom: style recalc duration 15ms 19ms +21%
large-graph-zoom: layout count 60 60 +0%
large-graph-zoom: style recalc count 63 64 +1%
large-graph-zoom: task duration 1049ms 1454ms +39%
large-graph-zoom: script duration 396ms 547ms +38%
large-graph-zoom: TBT 0ms 0ms +0%
large-graph-zoom: heap used 63.6 MB 69.7 MB +9%
large-graph-zoom: DOM nodes -277 8 -103%
large-graph-zoom: event listeners 6 8 +33%
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 6ms 7ms +20% z=-2.6
minimap-idle: layout count 0 0 +0%
minimap-idle: style recalc count 8 8 -6% z=-3.0
minimap-idle: task duration 446ms 561ms +26% z=0.7
minimap-idle: script duration 73ms 101ms +39% z=0.3
minimap-idle: TBT 0ms 0ms +0%
minimap-idle: heap used 56.9 MB 59.3 MB +4%
minimap-idle: DOM nodes -266 -129 -52% z=-108.3
minimap-idle: event listeners -197 -97 -51% z=-153.1
subgraph-dom-widget-clipping: avg frame time 17ms 17ms +0% z=0.1
subgraph-dom-widget-clipping: p95 frame time 17ms 17ms +0%
subgraph-dom-widget-clipping: layout duration 0ms 0ms +0%
subgraph-dom-widget-clipping: style recalc duration 11ms 11ms +3% z=-1.7
subgraph-dom-widget-clipping: layout count 0 0 +0%
subgraph-dom-widget-clipping: style recalc count 48 46 -5% z=-4.2
subgraph-dom-widget-clipping: task duration 317ms 414ms +31% z=1.9
subgraph-dom-widget-clipping: script duration 95ms 125ms +32% z=-0.6
subgraph-dom-widget-clipping: TBT 0ms 0ms +0%
subgraph-dom-widget-clipping: heap used 45.4 MB 60.3 MB +33%
subgraph-dom-widget-clipping: DOM nodes -271 -293 +8% z=-281.7
subgraph-dom-widget-clipping: event listeners -193 -196 +2% z=-36.4
subgraph-idle: avg frame time 17ms 17ms +0% z=0.4
subgraph-idle: p95 frame time 17ms 17ms +0%
subgraph-idle: layout duration 0ms 0ms +0%
subgraph-idle: style recalc duration 8ms 9ms +10% z=-1.9
subgraph-idle: layout count 0 0 +0%
subgraph-idle: style recalc count 10 9 -10% z=-2.9
subgraph-idle: task duration 333ms 390ms +17% z=0.6
subgraph-idle: script duration 12ms 15ms +28% z=-2.0
subgraph-idle: TBT 0ms 0ms +0%
subgraph-idle: heap used 59.2 MB 45.5 MB -23%
subgraph-idle: DOM nodes -274 -291 +6% z=-209.2
subgraph-idle: event listeners -195 -199 +2% variance too high
subgraph-mouse-sweep: avg frame time 17ms 17ms -0% z=-0.1
subgraph-mouse-sweep: p95 frame time 17ms 17ms +0%
subgraph-mouse-sweep: layout duration 4ms 6ms +82% ⚠️ z=5.2
subgraph-mouse-sweep: style recalc duration 29ms 44ms +51% z=0.6
subgraph-mouse-sweep: layout count 16 16 +0%
subgraph-mouse-sweep: style recalc count 76 76 +0% z=-2.1
subgraph-mouse-sweep: task duration 570ms 760ms +33% z=-0.1
subgraph-mouse-sweep: script duration 72ms 97ms +36% z=-0.5
subgraph-mouse-sweep: TBT 0ms 0ms +0%
subgraph-mouse-sweep: heap used 66.8 MB 50.8 MB -24%
subgraph-mouse-sweep: DOM nodes -264 -236 -11% z=-135.4
subgraph-mouse-sweep: event listeners -199 -199 +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 12ms 16ms +37%
subgraph-transition-enter: style recalc duration 25ms 34ms +34%
subgraph-transition-enter: layout count 15 13 -13%
subgraph-transition-enter: style recalc count 19 17 -11%
subgraph-transition-enter: task duration 630ms 879ms +40%
subgraph-transition-enter: script duration 24ms 32ms +37%
subgraph-transition-enter: TBT 98ms 167ms +70%
subgraph-transition-enter: heap used 93.7 MB 72.7 MB -22%
subgraph-transition-enter: DOM nodes 13673 12230 -11%
subgraph-transition-enter: event listeners 2533 842 -67%
viewport-pan-sweep: avg frame time 17ms 17ms +0%
viewport-pan-sweep: p95 frame time 17ms 17ms +0%
viewport-pan-sweep: layout duration 0ms 0ms +0%
viewport-pan-sweep: style recalc duration 54ms 59ms +9%
viewport-pan-sweep: layout count 0 0 +0%
viewport-pan-sweep: style recalc count 250 250 +0%
viewport-pan-sweep: task duration 3104ms 4200ms +35%
viewport-pan-sweep: script duration 1074ms 1361ms +27%
viewport-pan-sweep: TBT 0ms 0ms +0%
viewport-pan-sweep: heap used 65.4 MB 72.3 MB +11%
viewport-pan-sweep: DOM nodes -260 -123 -53%
viewport-pan-sweep: event listeners -126 -52 -59%
vue-large-graph-idle: avg frame time 17ms 17ms +3%
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 10675ms 12626ms +18%
vue-large-graph-idle: script duration 414ms 568ms +37%
vue-large-graph-idle: TBT 0ms 0ms +0%
vue-large-graph-idle: heap used 161.8 MB 154.1 MB -5%
vue-large-graph-idle: DOM nodes -3300 -3300 +0%
vue-large-graph-idle: event listeners -16368 -16378 +0%
vue-large-graph-pan: avg frame time 17ms 18ms +2%
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 18ms 21ms +15%
vue-large-graph-pan: layout count 0 0 +0%
vue-large-graph-pan: style recalc count 68 85 +24%
vue-large-graph-pan: task duration 13614ms 15959ms +17%
vue-large-graph-pan: script duration 694ms 848ms +22%
vue-large-graph-pan: TBT 3ms 28ms +833%
vue-large-graph-pan: heap used 166.7 MB 165.5 MB -1%
vue-large-graph-pan: DOM nodes -3300 -5807 +76%
vue-large-graph-pan: event listeners -16378 -16366 -0%
workflow-execution: avg frame time 17ms 17ms +0% z=0.6
workflow-execution: p95 frame time 17ms 17ms +0%
workflow-execution: layout duration 0ms 1ms +77% z=-5.8
workflow-execution: style recalc duration 15ms 24ms +61% z=-0.1
workflow-execution: layout count 2 3 +50% z=-3.6
workflow-execution: style recalc count 14 12 -14% z=-2.8
workflow-execution: task duration 156ms 152ms -2% ⚠️ z=2.7
workflow-execution: script duration 8ms 11ms +39% z=-6.1
workflow-execution: TBT 0ms 0ms +0%
workflow-execution: heap used 44.1 MB 49.3 MB +12%
workflow-execution: DOM nodes -202 -40 -80% z=-28.3
workflow-execution: event listeners -134 -34 -75% z=-19.5
Historical variance (last 15 runs)
Metric μ σ CV
canvas-idle: avg frame time 17ms 0ms 0.0%
canvas-idle: layout duration 0ms 0ms 0.0%
canvas-idle: style recalc duration 11ms 1ms 8.2%
canvas-idle: layout count 0 0 0.0%
canvas-idle: style recalc count 11 1 5.0%
canvas-idle: task duration 395ms 31ms 7.9%
canvas-idle: script duration 25ms 2ms 8.8%
canvas-idle: TBT 0ms 0ms 0.0%
canvas-idle: DOM nodes 23 1 5.6%
canvas-idle: event listeners 12 5 40.9%
canvas-mouse-sweep: avg frame time 17ms 0ms 0.0%
canvas-mouse-sweep: layout duration 4ms 0ms 5.4%
canvas-mouse-sweep: style recalc duration 43ms 3ms 7.4%
canvas-mouse-sweep: layout count 12 0 0.0%
canvas-mouse-sweep: style recalc count 79 2 3.0%
canvas-mouse-sweep: task duration 865ms 58ms 6.7%
canvas-mouse-sweep: script duration 136ms 6ms 4.8%
canvas-mouse-sweep: TBT 0ms 0ms 0.0%
canvas-mouse-sweep: DOM nodes 62 3 4.2%
canvas-mouse-sweep: event listeners 8 4 49.4%
canvas-zoom-sweep: avg frame time 17ms 0ms 0.0%
canvas-zoom-sweep: layout duration 1ms 0ms 7.0%
canvas-zoom-sweep: style recalc duration 19ms 2ms 8.0%
canvas-zoom-sweep: layout count 6 0 0.0%
canvas-zoom-sweep: style recalc count 31 0 1.5%
canvas-zoom-sweep: task duration 327ms 23ms 7.1%
canvas-zoom-sweep: script duration 27ms 3ms 11.1%
canvas-zoom-sweep: TBT 0ms 0ms 0.0%
canvas-zoom-sweep: DOM nodes 79 1 1.0%
canvas-zoom-sweep: event listeners 24 5 21.8%
dom-widget-clipping: avg frame time 17ms 0ms 0.0%
dom-widget-clipping: layout duration 0ms 0ms 0.0%
dom-widget-clipping: style recalc duration 10ms 1ms 8.0%
dom-widget-clipping: layout count 0 0 0.0%
dom-widget-clipping: style recalc count 13 0 3.8%
dom-widget-clipping: task duration 365ms 16ms 4.5%
dom-widget-clipping: script duration 68ms 3ms 4.8%
dom-widget-clipping: TBT 0ms 0ms 0.0%
dom-widget-clipping: DOM nodes 22 1 6.4%
dom-widget-clipping: event listeners 8 6 81.2%
large-graph-idle: avg frame time 17ms 0ms 0.0%
large-graph-idle: layout duration 0ms 0ms 0.0%
large-graph-idle: style recalc duration 12ms 1ms 8.6%
large-graph-idle: layout count 0 0 0.0%
large-graph-idle: style recalc count 12 0 2.7%
large-graph-idle: task duration 542ms 54ms 10.0%
large-graph-idle: script duration 102ms 11ms 10.3%
large-graph-idle: TBT 0ms 0ms 0.0%
large-graph-idle: DOM nodes 25 1 3.7%
large-graph-idle: event listeners 26 6 23.2%
large-graph-pan: avg frame time 17ms 0ms 0.0%
large-graph-pan: layout duration 0ms 0ms 0.0%
large-graph-pan: style recalc duration 17ms 1ms 4.6%
large-graph-pan: layout count 0 0 0.0%
large-graph-pan: style recalc count 70 1 0.9%
large-graph-pan: task duration 1082ms 43ms 4.0%
large-graph-pan: script duration 408ms 20ms 4.8%
large-graph-pan: TBT 0ms 0ms 0.0%
large-graph-pan: DOM nodes 19 2 8.7%
large-graph-pan: event listeners 5 1 16.8%
minimap-idle: avg frame time 17ms 0ms 0.0%
minimap-idle: layout duration 0ms 0ms 0.0%
minimap-idle: style recalc duration 10ms 1ms 8.6%
minimap-idle: layout count 0 0 0.0%
minimap-idle: style recalc count 10 1 7.1%
minimap-idle: task duration 527ms 47ms 9.0%
minimap-idle: script duration 98ms 10ms 10.1%
minimap-idle: TBT 0ms 0ms 0.0%
minimap-idle: DOM nodes 19 1 7.1%
minimap-idle: event listeners 5 1 14.4%
subgraph-dom-widget-clipping: avg frame time 17ms 0ms 0.0%
subgraph-dom-widget-clipping: layout duration 0ms 0ms 0.0%
subgraph-dom-widget-clipping: style recalc duration 13ms 1ms 7.4%
subgraph-dom-widget-clipping: layout count 0 0 0.0%
subgraph-dom-widget-clipping: style recalc count 48 1 1.2%
subgraph-dom-widget-clipping: task duration 378ms 18ms 4.9%
subgraph-dom-widget-clipping: script duration 128ms 6ms 4.9%
subgraph-dom-widget-clipping: TBT 0ms 0ms 0.0%
subgraph-dom-widget-clipping: DOM nodes 22 1 5.0%
subgraph-dom-widget-clipping: event listeners 16 6 36.0%
subgraph-idle: avg frame time 17ms 0ms 0.0%
subgraph-idle: layout duration 0ms 0ms 0.0%
subgraph-idle: style recalc duration 10ms 1ms 7.5%
subgraph-idle: layout count 0 0 0.0%
subgraph-idle: style recalc count 11 1 6.0%
subgraph-idle: task duration 370ms 31ms 8.5%
subgraph-idle: script duration 20ms 3ms 13.2%
subgraph-idle: TBT 0ms 0ms 0.0%
subgraph-idle: DOM nodes 22 1 6.9%
subgraph-idle: event listeners 10 7 64.5%
subgraph-mouse-sweep: avg frame time 17ms 0ms 0.0%
subgraph-mouse-sweep: layout duration 5ms 0ms 6.8%
subgraph-mouse-sweep: style recalc duration 42ms 3ms 7.8%
subgraph-mouse-sweep: layout count 16 0 0.0%
subgraph-mouse-sweep: style recalc count 80 2 2.4%
subgraph-mouse-sweep: task duration 766ms 69ms 9.0%
subgraph-mouse-sweep: script duration 101ms 7ms 6.5%
subgraph-mouse-sweep: TBT 0ms 0ms 0.0%
subgraph-mouse-sweep: DOM nodes 67 2 3.3%
subgraph-mouse-sweep: event listeners 8 4 52.6%
workflow-execution: avg frame time 17ms 0ms 0.0%
workflow-execution: layout duration 2ms 0ms 9.4%
workflow-execution: style recalc duration 24ms 2ms 9.1%
workflow-execution: layout count 5 1 11.0%
workflow-execution: style recalc count 18 2 11.5%
workflow-execution: task duration 123ms 11ms 8.8%
workflow-execution: script duration 29ms 3ms 10.2%
workflow-execution: TBT 0ms 0ms 0.0%
workflow-execution: DOM nodes 161 7 4.4%
workflow-execution: event listeners 52 4 8.4%
Trend (last 15 commits on main)
Metric Trend Dir Latest
canvas-idle: avg frame time ▆▃▆▁▆▃▆█▆▆▄▃▃▄▃ ➡️ 17ms
canvas-idle: p95 frame time ➡️ NaNms
canvas-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: style recalc duration ▇▇▆▆▃█▄▃▄▃▇▄▁▆▇ ➡️ 11ms
canvas-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
canvas-idle: style recalc count █▃▅▂▅▆▃▁▂▁▂▅▆▅▆ ➡️ 12
canvas-idle: task duration ▃▃▃▆▂▃▃▅▆▂█▃▁▃▃ ➡️ 391ms
canvas-idle: script duration ▄▃▅▇▂▅▃▆▇▅█▄▁▅▆ ➡️ 27ms
canvas-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-idle: heap used ➡️ NaN MB
canvas-idle: DOM nodes █▇▆▅▃▇▃▁▂▂▅▆▆▆▇ ➡️ 24
canvas-idle: event listeners ▅█▅▄▁▅▁▁▁▄▅▅▁▅▄ 📉 11
canvas-mouse-sweep: avg frame time ▆█▆▃▁▃▁▆▆▁▃▆▆▃▃ ➡️ 17ms
canvas-mouse-sweep: p95 frame time ➡️ NaNms
canvas-mouse-sweep: layout duration ▁▃▂▄▁▂▁▃▆▂█▇▆▄▃ ➡️ 4ms
canvas-mouse-sweep: style recalc duration ▄▄▂▄▁▂▃▃▅▄█▆▂▄▄ ➡️ 43ms
canvas-mouse-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 12
canvas-mouse-sweep: style recalc count █▅▄▃▂▂▁▄▄▅▆▅▂▇▄ ➡️ 79
canvas-mouse-sweep: task duration █▆▄▂▂▃▂▄▄▅█▆▁▆▄ ➡️ 868ms
canvas-mouse-sweep: script duration ▄▅▄▆▄▆▆▆▅▅█▆▁▅▆ ➡️ 139ms
canvas-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-mouse-sweep: heap used ➡️ NaN MB
canvas-mouse-sweep: DOM nodes █▅▃▃▁▂▂▃▂▄▆▅▃▅▅ ➡️ 64
canvas-mouse-sweep: event listeners █▁▁▁▁▁▇▁▁▁██▇▁█ 📈 13
canvas-zoom-sweep: avg frame time ▅▅█▄▅▁▁▁▅▁▁▅▄▅▁ ➡️ 17ms
canvas-zoom-sweep: p95 frame time ➡️ NaNms
canvas-zoom-sweep: layout duration ▆▅▅▄▁▁█▅▃▅▇▆▁▂▆ ➡️ 1ms
canvas-zoom-sweep: style recalc duration ▆▅▄▆▅▃█▆▇▅▇▄▁▃▅ ➡️ 20ms
canvas-zoom-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 6
canvas-zoom-sweep: style recalc count ▁▁▃▄▆▃▆█▄▄▆▁▆▁▆ ➡️ 32
canvas-zoom-sweep: task duration ▄▂▁▇▂▂▄▅▆▃█▄▁▁▅ ➡️ 338ms
canvas-zoom-sweep: script duration ▃▃▂▇▂▂▅▇▆▅█▄▁▂▆ ➡️ 30ms
canvas-zoom-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
canvas-zoom-sweep: heap used ➡️ NaN MB
canvas-zoom-sweep: DOM nodes ▄▃▁▅█▁▃▆▄▅▅▃▃▄▃ ➡️ 79
canvas-zoom-sweep: event listeners ▁▁▂▅█▂▁▅▁▅▅▄▁▅▁ ➡️ 19
dom-widget-clipping: avg frame time ▂▄▅▅▂▄█▇▅▇▇▅▅▁▇ ➡️ 17ms
dom-widget-clipping: p95 frame time ➡️ NaNms
dom-widget-clipping: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: style recalc duration ▆▆▂▆▄▃██▄▁▆▇▆▃▅ ➡️ 10ms
dom-widget-clipping: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
dom-widget-clipping: style recalc count ▇█▅█▅▄█▇▇▁▇▄▇▂▅ ➡️ 13
dom-widget-clipping: task duration ▃▃▁▅▄▃▅▆▅▂▇█▁▅▅ ➡️ 371ms
dom-widget-clipping: script duration ▅▄▄▆▆▅▇▇▆▃█▇▁▇▇ ➡️ 71ms
dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
dom-widget-clipping: heap used ➡️ NaN MB
dom-widget-clipping: DOM nodes ▇▇▄▇▅▄█▇▅▁▅▄▇▃▄ ➡️ 21
dom-widget-clipping: event listeners ▅▅▅▅▁▅██▁▁▁▁█▁▁ 📉 2
large-graph-idle: avg frame time ▅▅▅▅▅▂▁▂▄▅▄▂▂▅█ ➡️ 17ms
large-graph-idle: p95 frame time ➡️ NaNms
large-graph-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: style recalc duration ▅▅▅▆▄▅▃▄▅▅▆█▁▄▆ ➡️ 13ms
large-graph-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-idle: style recalc count █▆█▃▃▁▃▆▃▆▆▃▆██ ➡️ 12
large-graph-idle: task duration ▂▃▂▆▂▃▃▇▅▃██▁▂▅ ➡️ 569ms
large-graph-idle: script duration ▄▅▄▆▄▅▅▇▆▅█▆▁▃▆ ➡️ 110ms
large-graph-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-idle: heap used ➡️ NaN MB
large-graph-idle: DOM nodes ▆█▅▂▅▃▁▂▃▅▅▆▂▆▅ ➡️ 25
large-graph-idle: event listeners ███▇██▄▁▄▇▇█▂█▇ ➡️ 29
large-graph-pan: avg frame time ▆▃▃▆█▃▁█▆▆▆▆█▁▆ ➡️ 17ms
large-graph-pan: p95 frame time ➡️ NaNms
large-graph-pan: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: style recalc duration ▃▂▄▄▁▅▂▂▁▄▄█▃▁▂ ➡️ 17ms
large-graph-pan: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
large-graph-pan: style recalc count ▆▃█▂▃▂▂▂▁▇▅▃█▆▃ ➡️ 69
large-graph-pan: task duration ▄▃▄▆▄▄▄▆▄▄█▆▁▂▅ ➡️ 1100ms
large-graph-pan: script duration ▅▄▅▆▆▅▄▆▄▅█▄▁▄▅ ➡️ 413ms
large-graph-pan: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
large-graph-pan: heap used ➡️ NaN MB
large-graph-pan: DOM nodes ▅▃▆▂▄▁▃▁▁▅▁▂█▅▂ ➡️ 18
large-graph-pan: event listeners █▆█▁▁▆▁▁▃▆▁▃██▃ ➡️ 5
minimap-idle: avg frame time ▃▆▆▃█▁█▆▆▃▃▆█▆█ ➡️ 17ms
minimap-idle: p95 frame time ➡️ NaNms
minimap-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: style recalc duration ▄█▁█▅▅█▅▅▃▅▁▁▄▆ ➡️ 10ms
minimap-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
minimap-idle: style recalc count ▃▅▂▄█▃▆▁▂▅▂▁▅▆▃ ➡️ 9
minimap-idle: task duration ▃▄▁▅▁▃▄▅▇▃█▅▁▁▅ ➡️ 547ms
minimap-idle: script duration ▄▆▃▇▃▅▆▆▇▅█▅▁▃▆ ➡️ 106ms
minimap-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
minimap-idle: heap used ➡️ NaN MB
minimap-idle: DOM nodes ▃▅▂▄█▃▆▁▂▅▂▁▅▆▃ ➡️ 19
minimap-idle: event listeners ▃▃▆▁▁▁▃▁▁▆▁▃█▆▁ ➡️ 4
subgraph-dom-widget-clipping: avg frame time ▅▄▄▄▄▄█▄▄▄▃▁▆▃▃ ➡️ 17ms
subgraph-dom-widget-clipping: p95 frame time ➡️ NaNms
subgraph-dom-widget-clipping: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: style recalc duration ▂▄▃▅▅▃▂▅▇▃▄█▁▄▆ ➡️ 14ms
subgraph-dom-widget-clipping: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-dom-widget-clipping: style recalc count ▇█▆▃▆▃▁▆█▇▃▆▇█▅ ➡️ 48
subgraph-dom-widget-clipping: task duration ▂▃▃▆▅▅▂▅█▂▆█▁▂▇ ➡️ 398ms
subgraph-dom-widget-clipping: script duration ▃▃▃▄▅▅▂▄█▂▅▇▁▂▅ ➡️ 131ms
subgraph-dom-widget-clipping: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-dom-widget-clipping: heap used ➡️ NaN MB
subgraph-dom-widget-clipping: DOM nodes ▅▇▅▂▅▂▁▅▅▅▁▇▅█▄ ➡️ 22
subgraph-dom-widget-clipping: event listeners ▅▅▅▂▅▁▅██▁▁█▅█▅ 📈 16
subgraph-idle: avg frame time ▆▆█▁▆▃▆▆▆▃▆▁▃▆█ ➡️ 17ms
subgraph-idle: p95 frame time ➡️ NaNms
subgraph-idle: layout duration ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: style recalc duration ▁▇▃▆▂▄▂▃▃▆▆▄▃▇█ ➡️ 12ms
subgraph-idle: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0
subgraph-idle: style recalc count ▃▆▃▃▂▅▁▂▁▆▃▃██▇ ➡️ 12
subgraph-idle: task duration ▁▃▁▇▁▁▃▆▅▂█▅▁▁▄ ➡️ 378ms
subgraph-idle: script duration ▁▃▂▇▁▂▃▇▆▂█▅▂▁▅ ➡️ 22ms
subgraph-idle: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-idle: heap used ➡️ NaN MB
subgraph-idle: DOM nodes ▃▅▃▂▁▄▁▂▁▅▃▂▇█▇ ➡️ 24
subgraph-idle: event listeners ▁▅▁▁▁▁▁▁▁▅▄▁███ 📈 21
subgraph-mouse-sweep: avg frame time ▅▄▁▃▃▄▆▄▆▃▃█▁▃▃ ➡️ 17ms
subgraph-mouse-sweep: p95 frame time ➡️ NaNms
subgraph-mouse-sweep: layout duration ▁▄▄▄▃▃▅▅▅▂█▇▂▃▆ ➡️ 5ms
subgraph-mouse-sweep: style recalc duration ▃▂▄▅▂▃▄▅█▃█▆▁▂▅ ➡️ 43ms
subgraph-mouse-sweep: layout count ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 16
subgraph-mouse-sweep: style recalc count ▅▂▅▅▁▄▃▅█▅▆▄▂▄▅ ➡️ 81
subgraph-mouse-sweep: task duration ▃▂▄▅▂▄▄▅▇▄█▆▁▃▅ ➡️ 785ms
subgraph-mouse-sweep: script duration ▄▅▄▇▅▅▆▇▆▅██▁▄▆ ➡️ 105ms
subgraph-mouse-sweep: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
subgraph-mouse-sweep: heap used ➡️ NaN MB
subgraph-mouse-sweep: DOM nodes ▅▁▄▅▁▄▃▃█▅▅▄▂▅▃ ➡️ 66
subgraph-mouse-sweep: event listeners ▇▁▂▇▁▂▂▂█▇▂▂▇▇▂ 📈 5
workflow-execution: avg frame time ▆▆▆▄▆▆▃▄▁▄█▆▅▄▆ ➡️ 17ms
workflow-execution: p95 frame time ➡️ NaNms
workflow-execution: layout duration ▁▆▁▃▂▄▃▂▃▃▅█▄▂▅ ➡️ 2ms
workflow-execution: style recalc duration ▃▇▅▇▁▅▆▇█▁██▂▄▆ ➡️ 25ms
workflow-execution: layout count ▁█▂▃▂▃▃▁▃▃▄▃▂▃▂ ➡️ 5
workflow-execution: style recalc count ▃█▅▇▁▄▅▆▅▅▅▅▄▄▂ ➡️ 15
workflow-execution: task duration ▂▅▄▅▁▄▆▆▆▁▇█▁▃▃ ➡️ 120ms
workflow-execution: script duration ▄▃▄▄▃▅▄▅▆▂▇█▁▃▄ ➡️ 29ms
workflow-execution: TBT ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ➡️ 0ms
workflow-execution: heap used ➡️ NaN MB
workflow-execution: DOM nodes ▂█▃▆▁▄▃▅▃█▃▃▄▃▁ ➡️ 152
workflow-execution: event listeners ▅███▁▅███▁██▅█▅ ➡️ 49
Raw data
{
  "timestamp": "2026-07-11T12:38:51.533Z",
  "gitSha": "81350a69d9abe5b87917473c602c63120e9f0bf0",
  "branch": "feat/interactive-webcam-stream",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2111.7079999999933,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 8.083999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 467.62500000000006,
      "heapDeltaBytes": -3045612,
      "heapUsedBytes": 49662432,
      "domNodes": -290,
      "jsHeapTotalBytes": 17166336,
      "scriptDurationMs": 18.46,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-idle",
      "durationMs": 2055.534000000023,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.805999999999997,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 413.33299999999997,
      "heapDeltaBytes": -7499444,
      "heapUsedBytes": 62541368,
      "domNodes": -284,
      "jsHeapTotalBytes": 19546112,
      "scriptDurationMs": 16.075000000000003,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1847.1989999999892,
      "styleRecalcs": 72,
      "styleRecalcDurationMs": 43.989,
      "layouts": 12,
      "layoutDurationMs": 3.603,
      "taskDurationMs": 855.515,
      "heapDeltaBytes": -12736288,
      "heapUsedBytes": 57304748,
      "domNodes": -242,
      "jsHeapTotalBytes": 19021824,
      "scriptDurationMs": 127.04900000000002,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1882.0279999999912,
      "styleRecalcs": 76,
      "styleRecalcDurationMs": 43.693000000000005,
      "layouts": 12,
      "layoutDurationMs": 4.067,
      "taskDurationMs": 823.0189999999999,
      "heapDeltaBytes": -4703312,
      "heapUsedBytes": 47289548,
      "domNodes": -272,
      "jsHeapTotalBytes": 17952768,
      "scriptDurationMs": 124.01500000000001,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1757.6649999999745,
      "styleRecalcs": 30,
      "styleRecalcDurationMs": 18.374000000000002,
      "layouts": 6,
      "layoutDurationMs": 0.738,
      "taskDurationMs": 363.73900000000003,
      "heapDeltaBytes": -3646572,
      "heapUsedBytes": 66433776,
      "domNodes": -226,
      "jsHeapTotalBytes": 12730368,
      "scriptDurationMs": 19.046999999999997,
      "eventListeners": -184,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1753.1970000000001,
      "styleRecalcs": 33,
      "styleRecalcDurationMs": 19.386999999999997,
      "layouts": 6,
      "layoutDurationMs": 0.593,
      "taskDurationMs": 388.562,
      "heapDeltaBytes": 982060,
      "heapUsedBytes": 53175136,
      "domNodes": -240,
      "jsHeapTotalBytes": 6942720,
      "scriptDurationMs": 26.110999999999997,
      "eventListeners": -186,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 612.3890000000074,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 7.616000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 394.04699999999997,
      "heapDeltaBytes": 9085656,
      "heapUsedBytes": 61205092,
      "domNodes": -297,
      "jsHeapTotalBytes": 7729152,
      "scriptDurationMs": 59.471000000000004,
      "eventListeners": -203,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 600.3679999999463,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 9.822,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 362.279,
      "heapDeltaBytes": 10026892,
      "heapUsedBytes": 58823860,
      "domNodes": 18,
      "jsHeapTotalBytes": 18874368,
      "scriptDurationMs": 61.373,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2005.6140000000369,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 10.547,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 491.706,
      "heapDeltaBytes": -10698420,
      "heapUsedBytes": 65242368,
      "domNodes": 20,
      "jsHeapTotalBytes": 7745536,
      "scriptDurationMs": 87.601,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2046.671999999944,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 7.758999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 554.112,
      "heapDeltaBytes": -8522548,
      "heapUsedBytes": 59649288,
      "domNodes": -271,
      "jsHeapTotalBytes": -1847296,
      "scriptDurationMs": 101.625,
      "eventListeners": -197,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2130.1179999999817,
      "styleRecalcs": 69,
      "styleRecalcDurationMs": 18.451999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1229.268,
      "heapDeltaBytes": 14362724,
      "heapUsedBytes": 83899812,
      "domNodes": -273,
      "jsHeapTotalBytes": -856064,
      "scriptDurationMs": 464.38500000000005,
      "eventListeners": -197,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2156.358999999952,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 20.147000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1180.2510000000002,
      "heapDeltaBytes": 18878696,
      "heapUsedBytes": 79877152,
      "domNodes": -265,
      "jsHeapTotalBytes": 5169152,
      "scriptDurationMs": 404.506,
      "eventListeners": -128,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3258.7369999999964,
      "styleRecalcs": 62,
      "styleRecalcDurationMs": 16.906000000000006,
      "layouts": 60,
      "layoutDurationMs": 8.238000000000001,
      "taskDurationMs": 1493.318,
      "heapDeltaBytes": 17020872,
      "heapUsedBytes": 71810800,
      "domNodes": 4,
      "jsHeapTotalBytes": 1310720,
      "scriptDurationMs": 580.0419999999999,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3164.4659999999476,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 20.409999999999997,
      "layouts": 60,
      "layoutDurationMs": 8.02,
      "taskDurationMs": 1414.7409999999998,
      "heapDeltaBytes": 15992016,
      "heapUsedBytes": 74283992,
      "domNodes": 12,
      "jsHeapTotalBytes": 7864320,
      "scriptDurationMs": 513.894,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2036.5700000000402,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.5,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 495.041,
      "heapDeltaBytes": -10394400,
      "heapUsedBytes": 64741784,
      "domNodes": 18,
      "jsHeapTotalBytes": 10366976,
      "scriptDurationMs": 88.41600000000001,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 2032.2909999999865,
      "styleRecalcs": 6,
      "styleRecalcDurationMs": 6.328,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 626.8500000000001,
      "heapDeltaBytes": -16983468,
      "heapUsedBytes": 59591588,
      "domNodes": -276,
      "jsHeapTotalBytes": 3743744,
      "scriptDurationMs": 113.446,
      "eventListeners": -197,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 606.1699999999632,
      "styleRecalcs": 45,
      "styleRecalcDurationMs": 10.713000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 407.567,
      "heapDeltaBytes": 9261032,
      "heapUsedBytes": 61750880,
      "domNodes": -293,
      "jsHeapTotalBytes": 9302016,
      "scriptDurationMs": 122.63700000000001,
      "eventListeners": -195,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 605.3070000000389,
      "styleRecalcs": 46,
      "styleRecalcDurationMs": 11.551999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 419.859,
      "heapDeltaBytes": 12247460,
      "heapUsedBytes": 64675556,
      "domNodes": -293,
      "jsHeapTotalBytes": 4583424,
      "scriptDurationMs": 126.616,
      "eventListeners": -197,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2013.8539999999807,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 8.756,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 379.194,
      "heapDeltaBytes": -6225612,
      "heapUsedBytes": 46275080,
      "domNodes": -291,
      "jsHeapTotalBytes": 16904192,
      "scriptDurationMs": 13.781,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2015.2410000000032,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 9.034,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 400.61600000000004,
      "heapDeltaBytes": -3790396,
      "heapUsedBytes": 49106768,
      "domNodes": -291,
      "jsHeapTotalBytes": 16642048,
      "scriptDurationMs": 16.220000000000006,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1723.2500000000073,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 42.314,
      "layouts": 16,
      "layoutDurationMs": 4.893,
      "taskDurationMs": 755.5840000000001,
      "heapDeltaBytes": -16744392,
      "heapUsedBytes": 53343584,
      "domNodes": -236,
      "jsHeapTotalBytes": 20070400,
      "scriptDurationMs": 97.97,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1738.6129999999866,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 46.446,
      "layouts": 16,
      "layoutDurationMs": 7.855999999999998,
      "taskDurationMs": 765.2110000000001,
      "heapDeltaBytes": -16941016,
      "heapUsedBytes": 53165204,
      "domNodes": -235,
      "jsHeapTotalBytes": 18497536,
      "scriptDurationMs": 96.69800000000001,
      "eventListeners": -199,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "subgraph-transition-enter",
      "durationMs": 1098.9260000000058,
      "styleRecalcs": 17,
      "styleRecalcDurationMs": 33.51,
      "layouts": 13,
      "layoutDurationMs": 15.94,
      "taskDurationMs": 879.1190000000003,
      "heapDeltaBytes": 891084,
      "heapUsedBytes": 76196524,
      "domNodes": 12230,
      "jsHeapTotalBytes": 12800000,
      "scriptDurationMs": 32.471,
      "eventListeners": 842,
      "totalBlockingTimeMs": 167,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8165.61200000001,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 61.206,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4166.642,
      "heapDeltaBytes": -1072916,
      "heapUsedBytes": 71594224,
      "domNodes": 18,
      "jsHeapTotalBytes": 18145280,
      "scriptDurationMs": 1335.194,
      "eventListeners": 20,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8281.557999999904,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 57.608999999999995,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4232.648,
      "heapDeltaBytes": 20046108,
      "heapUsedBytes": 80096356,
      "domNodes": -263,
      "jsHeapTotalBytes": 4907008,
      "scriptDurationMs": 1387.574,
      "eventListeners": -124,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12393.40199999998,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12363.984999999999,
      "heapDeltaBytes": -45173696,
      "heapUsedBytes": 167059816,
      "domNodes": -3300,
      "jsHeapTotalBytes": -8155136,
      "scriptDurationMs": 555.8019999999999,
      "eventListeners": -16380,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.219999999999953,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 12905.342000000019,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 12887.622000000001,
      "heapDeltaBytes": -72159632,
      "heapUsedBytes": 156205988,
      "domNodes": -3300,
      "jsHeapTotalBytes": 7573504,
      "scriptDurationMs": 580.912,
      "eventListeners": -16376,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.219999999999953,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 15831.788000000017,
      "styleRecalcs": 82,
      "styleRecalcDurationMs": 20.380999999999982,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 15804.822000000002,
      "heapDeltaBytes": -60760312,
      "heapUsedBytes": 168436788,
      "domNodes": -8313,
      "jsHeapTotalBytes": 15462400,
      "scriptDurationMs": 842.549,
      "eventListeners": -16368,
      "totalBlockingTimeMs": 55,
      "frameDurationMs": 17.77333333333336,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 16141.188000000056,
      "styleRecalcs": 87,
      "styleRecalcDurationMs": 21.592999999999975,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 16112.490999999998,
      "heapDeltaBytes": -12682700,
      "heapUsedBytes": 178681372,
      "domNodes": -3300,
      "jsHeapTotalBytes": 14856192,
      "scriptDurationMs": 853.2590000000001,
      "eventListeners": -16364,
      "totalBlockingTimeMs": 1,
      "frameDurationMs": 17.22666666666664,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "workflow-execution",
      "durationMs": 454.02899999999136,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 19.060000000000002,
      "layouts": 3,
      "layoutDurationMs": 0.6950000000000001,
      "taskDurationMs": 104.428,
      "heapDeltaBytes": 4886680,
      "heapUsedBytes": 57115480,
      "domNodes": 128,
      "jsHeapTotalBytes": 0,
      "scriptDurationMs": 9.943999999999999,
      "eventListeners": 65,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "workflow-execution",
      "durationMs": 493.9679999999953,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 29.14,
      "layouts": 3,
      "layoutDurationMs": 0.7100000000000001,
      "taskDurationMs": 200.499,
      "heapDeltaBytes": -22893200,
      "heapUsedBytes": 46232040,
      "domNodes": -208,
      "jsHeapTotalBytes": 6438912,
      "scriptDurationMs": 11.924000000000001,
      "eventListeners": -132,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.699999999999818
    }
  ]
}

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The changes add interactive UI schemas and migration support, a validated websocket interaction protocol, webcam-based interactive sessions, connected-client prompt coordination, feature-flag wiring, and subgraph node-removal lifecycle cleanup.

Changes

Interactive streaming

Layer / File(s) Summary
Interactive UI schema and migration
packages/object-info-parser/src/schemas/nodeDefSchema.ts, packages/object-info-parser/src/__tests__/nodeDefSchema.test.ts, src/schemas/nodeDef/*, src/stores/nodeDefStore.ts
Interactive UI descriptors are validated, exposed on node definitions, and preserved by V1-to-V2 migration.
Interaction protocol and websocket API
src/services/interactionProtocol.*, src/scripts/api.*, src/config/clientFeatureFlags.json
Interaction controls and media use validated JSON and binary formats, while websocket dispatch, sending, reconnection tracking, and connected-client prompt requirements are added.
Interactive view sessions and node wiring
src/services/interactiveViewService.*, src/services/litegraphService.ts, src/scripts/app.ts
Interactive node widgets capture webcam frames, send and render media, handle reconnect states, and register for regular and subgraph nodes. The app requires a connected client for prompts containing interactive nodes.

Subgraph cleanup

Layer / File(s) Summary
Subgraph node removal lifecycle
src/lib/litegraph/src/LGraph.ts, src/lib/litegraph/src/LGraph.test.ts
LGraph.clear() invokes removal callbacks for nodes contained in stored subgraphs before clearing them, with a corresponding test.

Estimated code review effort: 5 (Critical) | ~90 minutes

Sequence Diagram(s)

sequenceDiagram
  participant InteractiveViewService
  participant ComfyApi
  participant BackendWebSocket
  participant InteractionProtocol
  InteractiveViewService->>ComfyApi: Send interaction control
  ComfyApi->>BackendWebSocket: Send JSON interaction message
  BackendWebSocket->>ComfyApi: Receive interaction control
  ComfyApi->>InteractionProtocol: Parse control
  ComfyApi->>InteractiveViewService: Dispatch interaction event
  InteractiveViewService->>ComfyApi: Send captured media
  ComfyApi->>InteractionProtocol: Encode media
  ComfyApi->>BackendWebSocket: Send binary media
  BackendWebSocket->>ComfyApi: Receive interaction media
  ComfyApi->>InteractionProtocol: Decode media
  ComfyApi->>InteractiveViewService: Dispatch interaction_media event
Loading

Suggested reviewers: dante01yoon, AustinMroz, christian-byrne


Important

Pre-merge checks failed

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

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Adr Compliance For Entity/Litegraph Changes ⚠️ Warning LGraph.clear now fires extension-facing onRemoved for nodes inside subgraphs, changing callback semantics under ADR 0008. Add migration guidance in docs/adr/0008 or release notes; consider gating the new onRemoved behavior for subgraph-contained nodes.
End-To-End Regression Coverage For Fixes ❓ Inconclusive PR title and commit subjects aren't available here, so the bug-fix signal required by the check can't be verified. Provide the PR title and commit subjects (or confirm there are none); then I can decide whether a browser_tests regression is required.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding interactive video stream widgets.
Description check ✅ Passed The description covers the core summary, protocol, demo behavior, and validation, though it omits several template sections like Changes and Review Focus.
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.
✨ 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 feat/interactive-webcam-stream

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

❤️ Share

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

@bigcat88 bigcat88 changed the title feat: add interactive video stream widgets [TEMP] feat: add interactive video stream widgets Jul 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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/services/interactionProtocol.ts`:
- Around line 100-126: Resolve the empty-media contract mismatch between
encodeInteractionMedia and decodeInteractionMedia. Either reject zero-length
media in encodeInteractionMedia, or adjust decodeInteractionMedia’s
metadata-length validation so buffers containing valid metadata with no media
payload are accepted; keep truncation and size checks consistent.

In `@src/services/interactiveViewService.test.ts`:
- Around line 115-262: Add tests covering the feature’s untested error and edge
branches: mock getUserMedia rejection and assert the camera-access message, open
an interaction without an active stream and assert a cancel control, and emit an
oversized interaction_media blob after opening a session with max_frame_bytes
and assert cancellation. Reuse the existing createNode, startWebcam, emit, and
mocks.controls helpers, and clean up each session.

In `@src/services/interactiveViewService.ts`:
- Around line 474-485: Replace the hardcoded webcam labels and status messages
in the interactive view construction with keys added to
src/locales/en/main.json, and retrieve them through the Vue I18n composition API
within the relevant service flow. Update the Start/Stop button text and both
status branches to use the localized translations while preserving the existing
feature-support logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d6a038f9-02e0-4fa7-b3b3-158f55bfe578

📥 Commits

Reviewing files that changed from the base of the PR and between 193bbab and d28875c.

📒 Files selected for processing (14)
  • packages/object-info-parser/src/__tests__/nodeDefSchema.test.ts
  • packages/object-info-parser/src/schemas/nodeDefSchema.ts
  • src/config/clientFeatureFlags.json
  • src/lib/litegraph/src/LGraph.test.ts
  • src/lib/litegraph/src/LGraph.ts
  • src/schemas/nodeDef/migration.test.ts
  • src/schemas/nodeDef/migration.ts
  • src/schemas/nodeDef/nodeDefSchemaV2.ts
  • src/scripts/api.ts
  • src/services/interactionProtocol.test.ts
  • src/services/interactionProtocol.ts
  • src/services/interactiveViewService.test.ts
  • src/services/interactiveViewService.ts
  • src/services/litegraphService.ts

Comment on lines +100 to +126
if (media.byteLength > MAX_INTERACTION_MEDIA_BYTES)
throw new Error('Interaction media is too large')
const result = new Uint8Array(8 + encoded.length + media.byteLength)
new DataView(result.buffer).setUint32(0, INTERACTION_MEDIA)
new DataView(result.buffer).setUint32(4, encoded.length)
result.set(encoded, 8)
result.set(new Uint8Array(media), 8 + encoded.length)
return result.buffer
}

export function decodeInteractionMedia(data: ArrayBuffer): {
metadata: InteractionMediaMetadata
media: ArrayBuffer
} {
if (data.byteLength < 9) throw new Error('Truncated interaction media')
const view = new DataView(data)
if (view.getUint32(0) !== INTERACTION_MEDIA)
throw new Error('Invalid interaction media type')
const length = view.getUint32(4)
if (
length === 0 ||
length > MAX_INTERACTION_METADATA_BYTES ||
8 + length >= data.byteLength
)
throw new Error('Invalid interaction metadata length')
if (data.byteLength - 8 - length > MAX_INTERACTION_MEDIA_BYTES)
throw new Error('Interaction media is too large')

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Encode/decode disagree on empty media.

encodeInteractionMedia places no lower bound on media.byteLength, so encoding a 0-byte frame yields a buffer of size 8 + metadataLength. decodeInteractionMedia then rejects it, because Line 122's 8 + length >= data.byteLength is true when there is no media payload. The two ends of the contract are inconsistent for the empty-media case. In practice JPEG frames are non-empty, so impact is limited, but the asymmetry is latent. Either forbid empty media in encode or relax the decode bound.

Proposed decode relaxation
-  if (
-    length === 0 ||
-    length > MAX_INTERACTION_METADATA_BYTES ||
-    8 + length >= data.byteLength
-  )
+  if (
+    length === 0 ||
+    length > MAX_INTERACTION_METADATA_BYTES ||
+    8 + length > data.byteLength
+  )
     throw new Error('Invalid interaction metadata length')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (media.byteLength > MAX_INTERACTION_MEDIA_BYTES)
throw new Error('Interaction media is too large')
const result = new Uint8Array(8 + encoded.length + media.byteLength)
new DataView(result.buffer).setUint32(0, INTERACTION_MEDIA)
new DataView(result.buffer).setUint32(4, encoded.length)
result.set(encoded, 8)
result.set(new Uint8Array(media), 8 + encoded.length)
return result.buffer
}
export function decodeInteractionMedia(data: ArrayBuffer): {
metadata: InteractionMediaMetadata
media: ArrayBuffer
} {
if (data.byteLength < 9) throw new Error('Truncated interaction media')
const view = new DataView(data)
if (view.getUint32(0) !== INTERACTION_MEDIA)
throw new Error('Invalid interaction media type')
const length = view.getUint32(4)
if (
length === 0 ||
length > MAX_INTERACTION_METADATA_BYTES ||
8 + length >= data.byteLength
)
throw new Error('Invalid interaction metadata length')
if (data.byteLength - 8 - length > MAX_INTERACTION_MEDIA_BYTES)
throw new Error('Interaction media is too large')
if (
length === 0 ||
length > MAX_INTERACTION_METADATA_BYTES ||
8 + length > data.byteLength
)
throw new Error('Invalid interaction metadata length')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/interactionProtocol.ts` around lines 100 - 126, Resolve the
empty-media contract mismatch between encodeInteractionMedia and
decodeInteractionMedia. Either reject zero-length media in
encodeInteractionMedia, or adjust decodeInteractionMedia’s metadata-length
validation so buffers containing valid metadata with no media payload are
accepted; keep truncation and size checks consistent.

Comment on lines +115 to +262
describe('interactive view sessions', () => {
it('does not request camera access when interactions are unavailable', () => {
mocks.serverSupportsFeature.mockReturnValue(false)
const { node, element } = createNode()

const start = element.querySelector('button')!
expect(start.hasAttribute('disabled')).toBe(true)
start.click()
expect(navigator.mediaDevices.getUserMedia).not.toHaveBeenCalled()

node.onRemoved?.()
})

it('keeps prompt routing stable and resends cancellation after reconnect', async () => {
const { node, element } = createNode()
await startWebcam(element)
const createBitmap = vi.fn().mockResolvedValue({
width: 1,
height: 1,
close: vi.fn()
})
vi.stubGlobal('createImageBitmap', createBitmap)
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue({
drawImage: vi.fn()
} as unknown as GPUCanvasContext)

emit('interaction', {
v: 1,
op: 'open',
interaction_id: 'interaction-a',
prompt_id: 'prompt-a',
display_node_id: '12',
group_id: 'video',
limits: { max_frame_bytes: 10 }
})
expect(mocks.controls).toContainEqual(
expect.objectContaining({ op: 'ready', prompt_id: 'prompt-a' })
)

emit('interaction_media', {
metadata: {
v: 1,
interaction_id: 'interaction-a',
channel: 'result',
seq: 0,
capture_ts_ms: 1,
mime: 'image/jpeg'
},
blob: new Blob([new Uint8Array(1)], { type: 'image/jpeg' })
})
await vi.waitFor(() => expect(createBitmap).toHaveBeenCalledOnce())

const controlCount = mocks.controls.length
emit('interaction', {
v: 1,
op: 'resume',
interaction_id: 'interaction-a',
prompt_id: 'prompt-b'
})
expect(mocks.controls).toHaveLength(controlCount)

emit('interaction', {
v: 1,
op: 'resume',
interaction_id: 'interaction-a',
prompt_id: 'prompt-a'
})
emit('interaction_media', {
metadata: {
v: 1,
interaction_id: 'interaction-a',
channel: 'result',
seq: 1,
capture_ts_ms: 2,
mime: 'image/jpeg'
},
blob: new Blob([new Uint8Array(11)], { type: 'image/jpeg' })
})
emit('interaction_media', {
metadata: {
v: 1,
interaction_id: 'interaction-a',
channel: 'result',
seq: 0,
capture_ts_ms: 1,
mime: 'image/jpeg'
},
blob: new Blob([new Uint8Array(1)], { type: 'image/jpeg' })
})
await Promise.resolve()
expect(createBitmap).toHaveBeenCalledOnce()

vi.useFakeTimers()
emit('reconnecting')
await vi.advanceTimersByTimeAsync(6500)
expect(mocks.controls.at(-1)).toMatchObject({
op: 'cancel',
prompt_id: 'prompt-a'
})

const cancellationCount = mocks.controls.length
emit('reconnected')
expect(mocks.controls).toHaveLength(cancellationCount)
emit('feature_flags')
expect(mocks.controls.at(-1)).toMatchObject({
op: 'cancel',
prompt_id: 'prompt-a'
})

node.onRemoved?.()
await vi.runAllTimersAsync()
})

it('cancels an active stream when reconnect negotiation drops support', async () => {
const { node, element } = createNode()
await startWebcam(element)
emit('interaction', {
v: 1,
op: 'open',
interaction_id: 'interaction-b',
prompt_id: 'prompt-b',
display_node_id: '12',
group_id: 'video'
})

emit('reconnecting')
emit('reconnected')
mocks.serverSupportsFeature.mockReturnValue(false)
emit('feature_flags')

expect(mocks.controls.at(-1)).toMatchObject({
op: 'cancel',
interaction_id: 'interaction-b',
prompt_id: 'prompt-b'
})
expect(element.textContent).toContain(
'Interactive streaming is unavailable.'
)

emit('interaction', {
v: 1,
op: 'closed',
interaction_id: 'interaction-b',
prompt_id: 'prompt-b'
})
node.onRemoved?.()
})
})

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.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding error/edge-path coverage.

The suite covers availability gating and reconnect negotiation well, but doesn't exercise the failure paths introduced by the feature: oversized captured frames (blob.size > maxFrameBytescancel), camera-denied (getUserMedia rejection → "Allow camera access to start."), or a session with no active stream receiving open (→ cancel). Adding these would guard the cancellation/error branches.

As per path instructions, cover "important edge cases introduced by the feature (e.g., invalid/malformed inputs, error paths, capability/negotiation failures)."

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

In `@src/services/interactiveViewService.test.ts` around lines 115 - 262, Add
tests covering the feature’s untested error and edge branches: mock getUserMedia
rejection and assert the camera-access message, open an interaction without an
active stream and assert a cancel control, and emit an oversized
interaction_media blob after opening a session with max_frame_bytes and assert
cancellation. Reuse the existing createNode, startWebcam, emit, and
mocks.controls helpers, and clean up each session.

Source: Path instructions

Comment on lines +474 to +485
const startButton = document.createElement('button')
startButton.className = 'rounded bg-primary px-3 py-1 text-primary-foreground'
startButton.textContent = 'Start Webcam'
const stopButton = document.createElement('button')
stopButton.className =
'rounded bg-secondary px-3 py-1 text-secondary-foreground'
stopButton.textContent = 'Stop Stream'
const status = document.createElement('span')
status.className = 'text-muted-foreground'
status.textContent = api.serverSupportsFeature('supports_interactions_v1')
? 'Webcam stopped.'
: 'Interactive streaming is unavailable.'

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.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## interactiveViewService.ts excerpt\n'
sed -n '430,540p' src/services/interactiveViewService.ts | cat -n

printf '\n## locale search\n'
rg -n "Start Webcam|Stop Stream|Webcam stopped|Interactive streaming is unavailable|interactive|webcam|stream" src/locales/en/main.json src/services/interactiveViewService.ts || true

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 8581


Route interactive view labels and status text through vue-i18n. The webcam start/stop labels and status messages in src/services/interactiveViewService.ts are still hardcoded; move them to src/locales/en/main.json and read them from the composition API.

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

In `@src/services/interactiveViewService.ts` around lines 474 - 485, Replace the
hardcoded webcam labels and status messages in the interactive view construction
with keys added to src/locales/en/main.json, and retrieve them through the Vue
I18n composition API within the relevant service flow. Update the Start/Stop
button text and both status branches to use the localized translations while
preserving the existing feature-support logic.

Source: Coding guidelines

@bigcat88 bigcat88 added the preview-gpu Creates a preview ephemeral environment for this PR (GPU available) label Jul 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/scripts/api.ts (1)

953-968: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Magic backpressure threshold.

2 * 1024 * 1024 bufferedAmount threshold is inlined. Extracting it to a named constant (e.g. INTERACTION_MEDIA_BACKPRESSURE_BYTES) would make the backpressure policy self-documenting and easier to tune.

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

In `@src/scripts/api.ts` around lines 953 - 968, Extract the inlined 2 * 1024 *
1024 bufferedAmount limit in sendInteractionMedia into a clearly named constant
such as INTERACTION_MEDIA_BACKPRESSURE_BYTES, and use that constant in the
backpressure condition without changing the existing behavior.
🤖 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/scripts/api.ts`:
- Around line 1109-1123: The waitForSocketSession method uses timer-based
polling for confirmation; replace this loop with an event- or promise-based wait
that resolves when the message handler’s status branch sets confirmedSocket and
confirmedClientId. Preserve the existing timeout behavior and
WebSocket/open-session validation, and reuse the confirmation state symbols
rather than introducing polling.

In `@src/scripts/app.ts`:
- Around line 1670-1678: In the requiresConnectedClient calculation, hoist
useNodeDefStore() into a local nodeDefStore constant before
Object.values(p.output).some, then access nodeDefsByName through that variable
inside the callback while preserving the existing interactive_ui check.

---

Outside diff comments:
In `@src/scripts/api.ts`:
- Around line 953-968: Extract the inlined 2 * 1024 * 1024 bufferedAmount limit
in sendInteractionMedia into a clearly named constant such as
INTERACTION_MEDIA_BACKPRESSURE_BYTES, and use that constant in the backpressure
condition without changing the existing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 474ee3af-cae6-4d67-a2a7-1c5a291a6afc

📥 Commits

Reviewing files that changed from the base of the PR and between d28875c and c3a13b5.

📒 Files selected for processing (4)
  • src/scripts/api.featureFlags.test.ts
  • src/scripts/api.ts
  • src/scripts/app.ts
  • src/stores/nodeDefStore.ts

Comment thread src/scripts/api.ts
Comment on lines +1109 to +1123
private async waitForSocketSession(
timeout = 5000
): Promise<string | undefined> {
const deadline = Date.now() + timeout
while (Date.now() < deadline) {
if (
this.socket &&
this.socket === this.confirmedSocket &&
this.socket.readyState === WebSocket.OPEN
) {
return this.confirmedClientId
}
await new Promise((resolve) => window.setTimeout(resolve, 50))
}
}

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.

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Busy-polling for socket confirmation.

waitForSocketSession polls this.confirmedSocket/readyState every 50ms for up to 5s. Since confirmation is set synchronously inside the message handler's status branch, an event/promise-based wait (e.g. resolving a pending promise from that branch, or awaiting a one-shot custom event) would avoid up to 50ms of wasted latency per call and avoid a timer-based loop.

#!/bin/bash
rg -n "confirmedSocket|confirmedClientId" src/scripts/api.ts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/scripts/api.ts` around lines 1109 - 1123, The waitForSocketSession method
uses timer-based polling for confirmation; replace this loop with an event- or
promise-based wait that resolves when the message handler’s status branch sets
confirmedSocket and confirmedClientId. Preserve the existing timeout behavior
and WebSocket/open-session validation, and reuse the confirmation state symbols
rather than introducing polling.

Comment thread src/scripts/app.ts
Comment on lines +1670 to +1678
const requiresConnectedClient = Object.values(p.output).some(
(node) =>
!!useNodeDefStore().nodeDefsByName[node.class_type]
?.interactive_ui?.length
)
const res = await api.queuePrompt(number, p, {
partialExecutionTargets: queueNodeIds,
previewMethod
previewMethod,
requireConnectedClient: requiresConnectedClient

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.

🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Hoist useNodeDefStore() out of the .some() callback.

useNodeDefStore() is called once per node in p.output. Hoisting it to a local const nodeDefStore = useNodeDefStore() before .some() avoids repeated store-instance lookups per node.

♻️ Proposed fix
-            const requiresConnectedClient = Object.values(p.output).some(
-              (node) =>
-                !!useNodeDefStore().nodeDefsByName[node.class_type]
-                  ?.interactive_ui?.length
-            )
+            const nodeDefStore = useNodeDefStore()
+            const requiresConnectedClient = Object.values(p.output).some(
+              (node) =>
+                !!nodeDefStore.nodeDefsByName[node.class_type]
+                  ?.interactive_ui?.length
+            )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const requiresConnectedClient = Object.values(p.output).some(
(node) =>
!!useNodeDefStore().nodeDefsByName[node.class_type]
?.interactive_ui?.length
)
const res = await api.queuePrompt(number, p, {
partialExecutionTargets: queueNodeIds,
previewMethod
previewMethod,
requireConnectedClient: requiresConnectedClient
const nodeDefStore = useNodeDefStore()
const requiresConnectedClient = Object.values(p.output).some(
(node) =>
!!nodeDefStore.nodeDefsByName[node.class_type]
?.interactive_ui?.length
)
const res = await api.queuePrompt(number, p, {
partialExecutionTargets: queueNodeIds,
previewMethod,
requireConnectedClient: requiresConnectedClient
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/scripts/app.ts` around lines 1670 - 1678, In the requiresConnectedClient
calculation, hoist useNodeDefStore() into a local nodeDefStore constant before
Object.values(p.output).some, then access nodeDefsByName through that variable
inside the callback while preserving the existing interactive_ui check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview-gpu Creates a preview ephemeral environment for this PR (GPU available)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant