Skip to content

fix: batch updateClipPath via requestAnimationFrame#9173

Merged
christian-byrne merged 1 commit intomainfrom
perf/fix-clippath-raf
Feb 27, 2026
Merged

fix: batch updateClipPath via requestAnimationFrame#9173
christian-byrne merged 1 commit intomainfrom
perf/fix-clippath-raf

Conversation

@christian-byrne
Copy link
Contributor

@christian-byrne christian-byrne commented Feb 24, 2026

Summary

Batch getBoundingClientRect() calls in updateClipPath via requestAnimationFrame to avoid forced synchronous layout.

Changes

  • What: Wrap the layout-reading portion of updateClipPath in requestAnimationFrame() with cancellation. Multiple rapid calls within the same frame are coalesced into a single layout read. Eliminates ~1,053 forced synchronous layouts per profiling session.

Review Focus

  • getBoundingClientRect() forces synchronous layout. When interleaved with style mutations (from PrimeVue useStyle, cursor writes, Vue VDOM patching), this creates layout thrashing — especially in Firefox where Stylo aggressively invalidates the entire style cache.
  • The RAF wrapper coalesces all calls within a frame into one, reading layout only once per frame. The cancelAnimationFrame ensures only the latest parameters are used.
  • willChange: 'clip-path' is included to hint the browser to optimize clip-path animations.

Stack

4 of 4 in Firefox perf fix stack. Depends on #9170.

┆Issue is synchronized with this Notion page by Unito

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

This PR adds performance optimization and comprehensive test coverage to the useDomClipping composable. The implementation introduces RAF-based batching to defer DOM measurements and clip-path calculations, reducing layout thrashing. A new test suite validates the coalescing behavior, style updates, and cancellation logic.

Changes

Cohort / File(s) Summary
RAF-based batching optimization
src/composables/element/useDomClipping.ts
Reworks updateClipPath to use requestAnimationFrame for batching. Cancels any pending RAF before scheduling new one, performs DOM measurements and clip-path calculation inside RAF callback, and updates style with computed clip-path and willChange values.
Comprehensive test suite
src/composables/element/useDomClipping.test.ts
Adds unit tests covering RAF coalescing behavior, style updates, RAF cancellation when called multiple times, clip-path polygon generation on intersection, and verification that no layout reads occur before RAF fires.

Sequence Diagram

sequenceDiagram
    participant Caller as Component
    participant Composable as useDomClipping
    participant RAF as requestAnimationFrame
    participant DOM as DOM API
    participant Style as Element.style

    Note over Caller,Style: Multiple rapid calls scenario
    Caller->>Composable: updateClipPath(element1)
    Composable->>RAF: cancelAnimationFrame(pendingRaf)
    Composable->>RAF: requestAnimationFrame(callback)
    Note right of RAF: pendingRaf = id1

    Caller->>Composable: updateClipPath(element2)
    Composable->>RAF: cancelAnimationFrame(id1)
    Composable->>RAF: requestAnimationFrame(callback)
    Note right of RAF: pendingRaf = id2, only id2 executes

    RAF->>Composable: callback() fires
    Composable->>DOM: getBoundingClientRect(element2)
    DOM-->>Composable: rect2
    Composable->>DOM: getBoundingClientRect(canvas)
    DOM-->>Composable: canvasRect
    Composable->>Composable: calculateClipPath(rect2, canvasRect)
    Composable->>Style: update clip-path & willChange
    Style-->>Composable: style updated
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A batching tale of animation frames,
Where multiple calls collapse to one,
DOM measurements deferred with care,
Layout thrashing now undone!
Tests verify each synchronized dance. 🎬

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: batch updateClipPath via requestAnimationFrame' clearly and specifically summarizes the main change—wrapping layout reads in RAF with coalescing.
Description check ✅ Passed The description follows the template with a clear Summary, detailed Changes section, and a comprehensive Review Focus explaining the performance problem, RAF coalescing strategy, and willChange optimization.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch perf/fix-clippath-raf

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

@github-actions
Copy link

github-actions bot commented Feb 24, 2026

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

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

@github-actions
Copy link

github-actions bot commented Feb 24, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 02/26/2026, 04:13:36 AM UTC

Links

@github-actions
Copy link

github-actions bot commented Feb 24, 2026

⚡ Performance Report

Metric Baseline PR Δ
canvas-idle: style recalcs 124 123 -1% ⚪
canvas-idle: layouts 0 0 +0% ⚪
canvas-idle: task duration 514ms 493ms -4% ⚪
canvas-mouse-sweep: style recalcs 188 187 -1% ⚪
canvas-mouse-sweep: layouts 12 12 +0% ⚪
canvas-mouse-sweep: task duration 1161ms 988ms -15% 🟢
dom-widget-clipping: style recalcs 43 42 -2% ⚪
dom-widget-clipping: layouts 0 0 +0% ⚪
dom-widget-clipping: task duration 372ms 388ms +4% ⚪
Raw data
{
  "timestamp": "2026-02-26T04:15:47.016Z",
  "gitSha": "6a790ce8ce5cea6d52d785af663ad2944a046573",
  "branch": "perf/fix-clippath-raf",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2029.500999999982,
      "styleRecalcs": 123,
      "styleRecalcDurationMs": 29.813,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 493.127,
      "heapDeltaBytes": -2025752
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2042.8150000000187,
      "styleRecalcs": 187,
      "styleRecalcDurationMs": 67.989,
      "layouts": 12,
      "layoutDurationMs": 4.443,
      "taskDurationMs": 988.272,
      "heapDeltaBytes": -3505488
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 566.7169999999828,
      "styleRecalcs": 42,
      "styleRecalcDurationMs": 13.960000000000003,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 387.579,
      "heapDeltaBytes": 7965944
    }
  ]
}

@christian-byrne christian-byrne force-pushed the perf/fix-clippath-raf branch 5 times, most recently from 59043e6 to f6bcb02 Compare February 26, 2026 03:29
Base automatically changed from perf/testing-infrastructure to main February 26, 2026 04:10
Wrap getBoundingClientRect() calls in updateClipPath inside
requestAnimationFrame() with cancellation. Multiple rapid calls
within the same frame are coalesced into a single layout read.

Eliminates ~1,053 forced synchronous layouts per profiling session
that were interleaved with style mutations from PrimeVue and cursor
updates, creating layout thrashing in Firefox.

Includes unit test validating RAF coalescing and cancellation.

Amp-Thread-ID: https://ampcode.com/threads/T-019c8ed0-59ad-720b-bc4f-6f52dc452844
@christian-byrne christian-byrne marked this pull request as ready for review February 26, 2026 04:12
@christian-byrne christian-byrne requested a review from a team as a code owner February 26, 2026 04:12
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Feb 26, 2026
@github-actions
Copy link

📦 Bundle: 4.43 MB gzip 🔴 +54 B

Details

Summary

  • Raw size: 20.8 MB baseline 20.8 MB — 🔴 +147 B
  • Gzip: 4.43 MB baseline 4.43 MB — 🔴 +54 B
  • Brotli: 3.42 MB baseline 3.42 MB — 🟢 -42 B
  • Bundles: 227 current • 227 baseline • 2 added / 2 removed

Category Glance
Graph Workspace 🔴 +147 B (986 kB) · Vendor & Third-Party ⚪ 0 B (8.84 MB) · Other ⚪ 0 B (7.74 MB) · Data & Services ⚪ 0 B (2.54 MB) · Panels & Settings ⚪ 0 B (435 kB) · Views & Navigation ⚪ 0 B (72.1 kB) · + 5 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-Bm4yGcaF.js (new) 17.9 kB 🔴 +17.9 kB 🔴 +6.35 kB 🔴 +5.5 kB
assets/index-vN3IQZJM.js (removed) 17.9 kB 🟢 -17.9 kB 🟢 -6.35 kB 🟢 -5.53 kB

Status: 1 added / 1 removed

Graph Workspace — 986 kB (baseline 986 kB) • 🔴 +147 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-CxzKZBdW.js (new) 986 kB 🔴 +986 kB 🔴 +212 kB 🔴 +160 kB
assets/GraphView-CHKSgoN-.js (removed) 986 kB 🟢 -986 kB 🟢 -212 kB 🟢 -160 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudAuthTimeoutView-Xo8A130R.js 4.91 kB 4.91 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudForgotPasswordView-DwyjPxGH.js 5.56 kB 5.56 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudLayoutView-BjfkLmUz.js 6.43 kB 6.43 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudLoginView-04FVJVcF.js 11.4 kB 11.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudSignupView-D3v9Du-w.js 9.37 kB 9.37 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudSorryContactSupportView-By_3y8r1.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudSubscriptionRedirectView-aUoXIHy4.js 4.75 kB 4.75 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudSurveyView-DTxFfIG7.js 15.5 kB 15.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/layout-CnBYY9vj.js 296 B 296 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserCheckView-CMJBGvdw.js 8.41 kB 8.41 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserSelectView-BOgQKsjC.js 4.5 kB 4.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Panels & Settings — 435 kB (baseline 435 kB) • ⚪ 0 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/AboutPanel-UsTZO3hp.js 9.79 kB 9.79 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/cloudRemoteConfig-D0V24PPf.js 1.44 kB 1.44 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/config-Tm3IpjoS.js 996 B 996 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ExtensionPanel-BICWcQ1b.js 9.38 kB 9.38 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/KeybindingPanel-DsQF_4w1.js 12.3 kB 12.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/LegacyCreditsPanel-C5FeKDSg.js 20.6 kB 20.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/refreshRemoteConfig-DwrigCkG.js 1.14 kB 1.14 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SecretsPanel-CzDNlIZm.js 21.5 kB 21.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ServerConfigPanel-CvCjtX77.js 6.44 kB 6.44 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-axAeoUrZ.js 28.8 kB 28.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-B2VYgS-a.js 29.9 kB 29.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BAN61mfQ.js 30.5 kB 30.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BNn9zn5v.js 38.5 kB 38.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CIIZ9BHS.js 28.7 kB 28.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CRqB5n8m.js 34.2 kB 34.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Cs_NVkbc.js 32.4 kB 32.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CYbBf6ct.js 27.9 kB 27.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DhTbqRj2.js 23.9 kB 23.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DW8FtJHC.js 24.5 kB 24.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-FzjdIfFm.js 27.8 kB 27.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionPanel-Tbf5adyJ.js 18.1 kB 18.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserPanel-Cel9ycSv.js 6.16 kB 6.16 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
User & Accounts — 16 kB (baseline 16 kB) • ⚪ 0 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-CqQS_4lN.js 3.4 kB 3.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/auth-DcpRWmhe.js 357 B 357 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/firebaseAuthStore-CqDTroe5.js 788 B 788 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/PasswordFields-DLbVLg8O.js 4.51 kB 4.51 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SignUpForm-DzJQBWxp.js 3.01 kB 3.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UpdatePasswordContent-9V1A64wH.js 2.37 kB 2.37 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WorkspaceProfilePic-CK1MFS2B.js 1.57 kB 1.57 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Editors & Dialogs — 736 B (baseline 736 B) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog-CkWDQjBE.js 736 B 736 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
UI Components — 47.1 kB (baseline 47.1 kB) • ⚪ 0 B

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/Button-D1z3poyI.js 2.98 kB 2.98 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudBadge-BrLRSAsL.js 1.24 kB 1.24 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/cloudFeedbackTopbarButton-CZ0suqLB.js 1.59 kB 1.59 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyQueueButton-B_W-sNLD.js 8.02 kB 8.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyQueueButton-LQwXuB5A.js 793 B 793 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/FormSearchInput--hCtYSQV.js 3.73 kB 3.73 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ScrubableNumberInput-DecBFGbM.js 5.94 kB 5.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscribeButton-BZgtOo2g.js 2.48 kB 2.48 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/TopbarBadge-Dd3SHM68.js 7.44 kB 7.44 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserAvatar-CnQQLXB-.js 1.17 kB 1.17 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useTerminalTabs-CoLdDyV1.js 9.84 kB 9.84 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetButton-VJ452z4N.js 1.84 kB 1.84 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Data & Services — 2.54 MB (baseline 2.54 MB) • ⚪ 0 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/api-BUYukRxA.js 676 kB 676 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/audioService-D5jfSvoA.js 1.73 kB 1.73 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/bootstrapStore-jz0ILcOG.js 2.08 kB 2.08 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/dialogService-Diyqrb-Z.js 1.74 MB 1.74 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/dialogService-tEpUN0yI.js 725 B 725 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/extensionStore-BV2zsE9g.js 12.1 kB 12.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/keybindingService-DVGQJ-cn.js 6.52 kB 6.52 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/load3dService-BWe74-Gk.js 91 kB 91 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/releaseStore-a7jysDhF.js 760 B 760 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/releaseStore-D97HMhL0.js 7.96 kB 7.96 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/serverConfigStore-CkFMhmxK.js 2.32 kB 2.32 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settingStore-Cyq_Iclk.js 744 B 744 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/userStore-BO4t-5AZ.js 1.85 kB 1.85 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/workflowDraftStore-CCVPnjcx.js 736 B 736 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
Utilities & Hooks — 55.5 kB (baseline 55.5 kB) • ⚪ 0 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/_plugin-vue_export-helper-ralzwvFM.js 315 B 315 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/audioUtils-G1Th5rWn.js 858 B 858 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/colorUtil-CY7QMUhQ.js 7 kB 7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/envUtil-Clzmwvt4.js 466 B 466 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-Cddas8Zl.js 1.56 kB 1.56 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SkeletonUtils-BputJAFn.js 133 B 133 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/subscriptionCheckoutUtil-BlWMG8mX.js 2.53 kB 2.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useCurrentUser-C5HGmWPl.js 722 B 722 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useErrorHandling-BOoJuLPx.js 1.5 kB 1.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useExternalLink-D2JSE_FX.js 1.66 kB 1.66 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useFeatureFlags-RyrV8Xv_.js 4.14 kB 4.14 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useLoad3d-2MtIObFW.js 859 B 859 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useLoad3d-DuKA1u8V.js 14.6 kB 14.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useLoad3dViewer-B0dKACGq.js 14.1 kB 14.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useLoad3dViewer-C0J1iLiw.js 838 B 838 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useWorkspaceSwitch-1QBzMUJo.js 1.25 kB 1.25 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useWorkspaceUI-BSmgkgVw.js 3 kB 3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Vendor & Third-Party — 8.84 MB (baseline 8.84 MB) • ⚪ 0 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-axios-Cp6hch1I.js 70.7 kB 70.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-chart-BxkFiWzp.js 399 kB 399 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-firebase-BvMr43CG.js 836 kB 836 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-i18n-DNX73mqE.js 133 kB 133 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-markdown-D5S6AC80.js 103 kB 103 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-other-DrYd4O-6.js 1.52 MB 1.52 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-primevue-BnCPTL0g.js 1.73 MB 1.73 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-reka-ui-DVmi2O2Z.js 388 kB 388 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-sentry-SQwstEKc.js 182 kB 182 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-LBLOE6BD.js 1.8 MB 1.8 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-BnYkbQDM.js 634 kB 634 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-core-DtiQ1dr9.js 311 kB 311 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vueuse-D2jVNnmE.js 113 kB 113 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-DK6Ygydn.js 374 kB 374 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-yjs-CP_4YO8u.js 143 kB 143 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-zod-DcCUUPIi.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Other — 7.74 MB (baseline 7.74 MB) • ⚪ 0 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/AnimationControls-e1OB6oJR.js 4.61 kB 4.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ApiNodesSignInContent-xPxNG93N.js 2.69 kB 2.69 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/AudioPreviewPlayer-BTVVmCZZ.js 10.9 kB 10.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/auto-BTnZwrs2.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/BaseViewTemplate-C7TdQsa4.js 1.78 kB 1.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CancelSubscriptionDialogContent-Cwd3J0Y3.js 4.79 kB 4.79 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/changeTracker-Bu1yKe_F.js 9.38 kB 9.38 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/changeTracker-C45Uialp.js 757 B 757 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/cloudBadges-C_YrGTkK.js 1.37 kB 1.37 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudRunButtonWrapper-BKyw50BE.js 1.68 kB 1.68 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/cloudSessionCookie-CjejHRVF.js 3.1 kB 3.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/cloudSubscription-BYagZTGR.js 1.33 kB 1.33 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/comfy-logo-single-D9MrYETV.js 198 B 198 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyOrgHeader-CuEodz4y.js 910 B 910 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Bdca9E4N.js 16.7 kB 16.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Boqbp4FE.js 16.9 kB 16.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-ClFTZZY8.js 18.8 kB 18.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CnE4PBoq.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CNvwc2P-.js 17.5 kB 17.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CsOV8HcO.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DBggpNhX.js 15.2 kB 15.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DC4TJj5_.js 15.1 kB 15.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DPVHiFlP.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-eBbhcNBX.js 17.5 kB 17.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-oGOH5erQ.js 15.9 kB 15.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/constants-htt0vt7m.js 579 B 579 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/core-hC2jixDY.js 72.7 kB 72.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CreateWorkspaceDialogContent-CusEOU2w.js 5.53 kB 5.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CurrentUserPopoverWorkspace-DDlKVYXc.js 19.9 kB 19.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/DeleteWorkspaceDialogContent-fzFM-fLR.js 4.23 kB 4.23 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/EditWorkspaceDialogContent-BwvToat1.js 5.33 kB 5.33 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/FreeTierDialogContent-Va2igSTW.js 5.39 kB 5.39 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/GlobalToast-CQ-ehHXO.js 2.91 kB 2.91 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/graphHasMissingNodes-TFq6H1VK.js 761 B 761 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/groupNode-CxRF1w7d.js 71.8 kB 71.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-BY6i52_i.js 526 kB 526 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-C3T8tTh4.js 199 B 199 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/InviteMemberDialogContent-C4CUTYgY.js 7.38 kB 7.38 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/InviteMemberUpsellDialogContent-J2gTiPm8.js 3.82 kB 3.82 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/LeaveWorkspaceDialogContent-BUjeFJFn.js 4.06 kB 4.06 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Load3D-BZ8bQhYg.js 16.2 kB 16.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Load3D-CFMSkxNv.js 1.07 kB 1.07 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/load3d-G_Lt6IR-.js 14.7 kB 14.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Load3DConfiguration-DO3a8FsY.js 6.27 kB 6.27 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Load3DControls-CN9E9act.js 30.9 kB 30.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Load3dViewerContent-C_OMMOH3.js 23 kB 23 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Load3dViewerContent-DHJpH17_.js 993 B 993 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B_4-yaOE.js 154 kB 154 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-C1cLXKzG.js 151 kB 151 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Cm0JA816.js 176 kB 176 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CUFqlSSt.js 205 kB 205 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DCM5-M4s.js 128 kB 128 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DGdk9JD_.js 183 kB 183 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DJEwO7w1.js 146 kB 146 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Du9Mttqc.js 169 kB 169 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-nDJB1-zI.js 147 kB 147 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-ss5tT-9f.js 130 kB 130 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-ZrG2D_5c.js 149 kB 149 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Media3DTop-CouvCIvd.js 1.82 kB 1.82 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-CJNtJ1Vf.js 1.43 kB 1.43 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-CsrUK1I8.js 1.75 kB 1.75 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaOtherTop-CGnATDyM.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaTextTop-DDsKiNmL.js 1.01 kB 1.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-BvDxKWex.js 2.77 kB 2.77 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nightlyBadges-DDQcFnkC.js 1 kB 1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs--tG2_kTy.js 390 kB 390 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-B1CcRnYm.js 443 kB 443 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BOkmcPF-.js 397 kB 397 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C9THNc4O.js 442 kB 442 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CBfcBh4b.js 362 kB 362 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CZlGuvdG.js 385 kB 385 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DB-aHpeo.js 393 kB 393 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DPlU1YTi.js 358 kB 358 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-mmU8eLDX.js 409 kB 409 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-rItghg3p.js 483 kB 483 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-s5_Usl0F.js 393 kB 393 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeTemplates-DDhV53Ia.js 9.29 kB 9.29 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/onboardingCloudRoutes-B8XuUHMX.js 5.41 kB 5.41 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Popover-BIYdg9E5.js 3.65 kB 3.65 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Preview3d-BJu2qqxz.js 4.81 kB 4.81 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/previousFullPath-dOB52iw7.js 1.39 kB 1.39 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/RemoveMemberDialogContent-5MXUXXHc.js 4.04 kB 4.04 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/RevokeInviteDialogContent-DTh0AMjK.js 3.95 kB 3.95 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-DLICfi3-.js 1.97 kB 1.97 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/saveMesh-BxVoOFYF.js 3.38 kB 3.38 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SelectValue-C_7cycpB.js 8.94 kB 8.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SignInContent-StOaxmKl.js 18.9 kB 18.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/signInSchema-DE78q_5I.js 1.53 kB 1.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Slider-DVkw5nPu.js 3.52 kB 3.52 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/src-CbNGuSYA.js 251 B 251 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscribeToRun-C1QGkyUW.js 2.2 kB 2.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionBenefits-BYIJyGB1.js 2.01 kB 2.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionPanelContentWorkspace-CxiwZVjP.js 21.5 kB 21.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionPanelContentWorkspace-e5Xnnjex.js 920 B 920 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionRequiredDialogContent-BuSP-6Tn.js 25.7 kB 25.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionRequiredDialogContentWorkspace-BT3uE5H-.js 46.3 kB 46.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/telemetry-zZf2dHJ2.js 226 B 226 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/tierBenefits-BFnC52kd.js 3.66 kB 3.66 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/types-DT3N7am7.js 204 B 204 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ValueControlPopover-CQo9kQkk.js 4.92 kB 4.92 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/VideoPlayOverlay-D-ZhKuWc.js 1.35 kB 1.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-NeEr3XWN.js 586 B 586 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-BYbwNME9.js 283 B 283 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-GzA4D-L-.js 3.19 kB 3.19 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-DhNMsSAU.js 2.21 kB 2.21 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetColorPicker-ykkxGhLh.js 2.9 kB 2.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetCurve-CNAR8Elf.js 9.35 kB 9.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetGalleria-CmUesy36.js 3.61 kB 3.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-Dfo8GHhe.js 6.95 kB 6.95 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCrop-CaoUgIXC.js 22.1 kB 22.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputNumber-C090TijP.js 469 B 469 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputNumber-D9yHpoqG.js 18.7 kB 18.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputText-DASYqR-h.js 1.86 kB 1.86 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetLayoutField-O1dUcz97.js 1.98 kB 1.98 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetLegacy-BfLAARSY.js 745 B 745 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetMarkdown-BryTE-VI.js 2.93 kB 2.93 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-_HM7alVA.js 1.1 kB 1.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetRecordAudio-DRCfrXxb.js 17.3 kB 17.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetSelect-C3oRf5vU.js 58.1 kB 58.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetTextarea-C1btAxfw.js 3.96 kB 3.96 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetToggleSwitch-6Htnt0a8.js 6.8 kB 6.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetTypes-Br_tbhcL.js 393 B 393 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetWithControl-DTXO0Y7G.js 4.1 kB 4.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WorkspacePanelContent-CwX5E_4c.js 29.3 kB 29.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/composables/element/useDomClipping.ts (1)

108-125: Consider cancelling pending RAF on scope disposal.

The RAF batching implementation is correct and achieves the performance goal. However, if the composable's scope is disposed (e.g., component unmounts) while a RAF is pending, the callback will still execute.

This is low-risk since Vue refs tolerate writes after unmount, but for completeness you could cancel the pending RAF when the scope is disposed using onScopeDispose.

♻️ Optional cleanup on scope disposal
+import { onScopeDispose } from 'vue'
 import type { CSSProperties } from 'vue'
 import { ref } from 'vue'
   let pendingRaf = 0
+
+  onScopeDispose(() => {
+    if (pendingRaf) cancelAnimationFrame(pendingRaf)
+  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/composables/element/useDomClipping.ts` around lines 108 - 125, The
pending requestAnimationFrame created in useDomClipping
(pendingRaf/requestAnimationFrame block) isn't cancelled when the composable's
scope is disposed; add an onScopeDispose handler in the same composable that
checks pendingRaf and calls cancelAnimationFrame(pendingRaf) (and resets
pendingRaf to 0) to prevent the RAF callback from running after unmount and
touching style.value or DOM.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/composables/element/useDomClipping.ts`:
- Around line 108-125: The pending requestAnimationFrame created in
useDomClipping (pendingRaf/requestAnimationFrame block) isn't cancelled when the
composable's scope is disposed; add an onScopeDispose handler in the same
composable that checks pendingRaf and calls cancelAnimationFrame(pendingRaf)
(and resets pendingRaf to 0) to prevent the RAF callback from running after
unmount and touching style.value or DOM.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e215b3 and f0808a5.

📒 Files selected for processing (2)
  • src/composables/element/useDomClipping.test.ts
  • src/composables/element/useDomClipping.ts

Copy link
Member

@pythongosssss pythongosssss left a comment

Choose a reason for hiding this comment

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

lgtm

@christian-byrne christian-byrne merged commit 1054ba8 into main Feb 27, 2026
37 checks passed
@christian-byrne christian-byrne deleted the perf/fix-clippath-raf branch February 27, 2026 02:53
christian-byrne added a commit that referenced this pull request Feb 27, 2026
## Summary

Batch `getBoundingClientRect()` calls in `updateClipPath` via
`requestAnimationFrame` to avoid forced synchronous layout.

## Changes

- **What**: Wrap the layout-reading portion of `updateClipPath` in
`requestAnimationFrame()` with cancellation. Multiple rapid calls within
the same frame are coalesced into a single layout read. Eliminates
~1,053 forced synchronous layouts per profiling session.

## Review Focus

- `getBoundingClientRect()` forces synchronous layout. When interleaved
with style mutations (from PrimeVue `useStyle`, cursor writes, Vue VDOM
patching), this creates layout thrashing — especially in Firefox where
Stylo aggressively invalidates the entire style cache.
- The RAF wrapper coalesces all calls within a frame into one, reading
layout only once per frame. The `cancelAnimationFrame` ensures only the
latest parameters are used.
- `willChange: 'clip-path'` is included to hint the browser to optimize
clip-path animations.

## Stack

4 of 4 in Firefox perf fix stack. Depends on #9170.

<!-- Fixes #ISSUE_NUMBER -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9173-fix-batch-updateClipPath-via-requestAnimationFrame-3116d73d3650810392f7fba7ea5ceb6f)
by [Unito](https://www.unito.io)
@christian-byrne christian-byrne added core/1.40 Backport PRs for core 1.40 needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch labels Mar 8, 2026
github-actions bot pushed a commit that referenced this pull request Mar 8, 2026
## Summary

Batch `getBoundingClientRect()` calls in `updateClipPath` via
`requestAnimationFrame` to avoid forced synchronous layout.

## Changes

- **What**: Wrap the layout-reading portion of `updateClipPath` in
`requestAnimationFrame()` with cancellation. Multiple rapid calls within
the same frame are coalesced into a single layout read. Eliminates
~1,053 forced synchronous layouts per profiling session.

## Review Focus

- `getBoundingClientRect()` forces synchronous layout. When interleaved
with style mutations (from PrimeVue `useStyle`, cursor writes, Vue VDOM
patching), this creates layout thrashing — especially in Firefox where
Stylo aggressively invalidates the entire style cache.
- The RAF wrapper coalesces all calls within a frame into one, reading
layout only once per frame. The `cancelAnimationFrame` ensures only the
latest parameters are used.
- `willChange: 'clip-path'` is included to hint the browser to optimize
clip-path animations.

## Stack

4 of 4 in Firefox perf fix stack. Depends on #9170.

<!-- Fixes #ISSUE_NUMBER -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9173-fix-batch-updateClipPath-via-requestAnimationFrame-3116d73d3650810392f7fba7ea5ceb6f)
by [Unito](https://www.unito.io)
@comfy-pr-bot
Copy link
Member

@christian-byrne Successfully backported to #9588

@github-actions github-actions bot removed the needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch label Mar 8, 2026
christian-byrne added a commit that referenced this pull request Mar 8, 2026
…ame (#9588)

Backport of #9173 to `core/1.40`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9588-backport-core-1-40-fix-batch-updateClipPath-via-requestAnimationFrame-31d6d73d365081f7a8cffad0de3ab38b)
by [Unito](https://www.unito.io)

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core/1.40 Backport PRs for core 1.40 perf:speed size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants